[Move refactor] Move tabs to interface/widgets (#6235)

* Move tabs to interface/widgets.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2025-10-09 14:51:47 +02:00 committed by GitHub
parent d9c65d4ae0
commit b8983f27ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
134 changed files with 111 additions and 112 deletions

View file

@ -2,7 +2,7 @@
#include "../../../game/board/card_item.h"
#include "../../../interface/card_picture_loader/card_picture_loader.h"
#include "../../../tabs/tab_supervisor.h"
#include "../../../interface/widgets/tabs/tab_supervisor.h"
#include "../../window_main.h"
#include <QMenu>

View file

@ -8,7 +8,7 @@
#ifndef DECK_EDITOR_CARD_INFO_DOCK_WIDGET_H
#define DECK_EDITOR_CARD_INFO_DOCK_WIDGET_H
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include "../cards/card_info_frame_widget.h"
#include <QDockWidget>

View file

@ -1,8 +1,8 @@
#include "deck_editor_database_display_widget.h"
#include "../../../filters/syntax_help.h"
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../../../tabs/tab_supervisor.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include "../../../interface/widgets/tabs/tab_supervisor.h"
#include "../../pixel_map_generator.h"
#include <QClipboard>

View file

@ -8,7 +8,7 @@
#ifndef DECK_EDITOR_DATABASE_DISPLAY_WIDGET_H
#define DECK_EDITOR_DATABASE_DISPLAY_WIDGET_H
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include "../utility/custom_line_edit.h"
#include <QHBoxLayout>

View file

@ -8,7 +8,7 @@
#ifndef DECK_EDITOR_DECK_DOCK_WIDGET_H
#define DECK_EDITOR_DECK_DOCK_WIDGET_H
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include "../utility/custom_line_edit.h"
#include "../visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h"

View file

@ -8,7 +8,7 @@
#ifndef DECK_EDITOR_FILTER_DOCK_WIDGET_H
#define DECK_EDITOR_FILTER_DOCK_WIDGET_H
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include <QDockWidget>
#include <QTreeView>

View file

@ -1,6 +1,6 @@
#include "deck_editor_printing_selector_dock_widget.h"
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include <QVBoxLayout>

View file

@ -7,7 +7,7 @@
#ifndef DECK_EDITOR_PRINTING_SELECTOR_DOCK_WIDGET_H
#define DECK_EDITOR_PRINTING_SELECTOR_DOCK_WIDGET_H
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include "../printing_selector/printing_selector.h"
#include <QDockWidget>

View file

@ -1,6 +1,6 @@
#include "home_widget.h"
#include "../../../tabs/tab_supervisor.h"
#include "../../../interface/widgets/tabs/tab_supervisor.h"
#include "../../window_main.h"
#include "background_sources.h"
#include "home_styled_button.h"

View file

@ -7,7 +7,7 @@
#ifndef HOME_WIDGET_H
#define HOME_WIDGET_H
#include "../../../tabs/tab_supervisor.h"
#include "../../../interface/widgets/tabs/tab_supervisor.h"
#include "../cards/card_info_picture_art_crop_widget.h"
#include "home_styled_button.h"

View file

@ -7,7 +7,7 @@
#ifndef DECK_EDITOR_MENU_H
#define DECK_EDITOR_MENU_H
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include <QMenu>

View file

@ -8,7 +8,7 @@
#ifndef CARD_AMOUNT_WIDGET_H
#define CARD_AMOUNT_WIDGET_H
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include "../general/display/dynamic_font_size_push_button.h"
#include <QHBoxLayout>

View file

@ -7,7 +7,7 @@
#ifndef PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H
#define PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include "printing_selector_card_overlay_widget.h"
#include "set_name_and_collectors_number_display_widget.h"

View file

@ -7,7 +7,7 @@
#ifndef PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H
#define PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include "../cards/card_info_picture_widget.h"
#include "all_zones_card_amount_widget.h"
#include "card_amount_widget.h"

View file

@ -1,6 +1,6 @@
#include "replay_manager.h"
#include "../../../tabs/tab_game.h"
#include "../interface/widgets/tabs/tab_game.h"
#include <QHBoxLayout>
#include <QToolButton>

View file

@ -2,7 +2,7 @@
#include "../../client/sound_engine.h"
#include "../../interface/pixel_map_generator.h"
#include "../../tabs/tab_account.h"
#include "../../interface/widgets/tabs/tab_account.h"
#include "../user/user_context_menu.h"
#include "../user/user_list_manager.h"
#include "../user/user_list_proxy.h"

View file

@ -8,7 +8,7 @@
#ifndef CHATVIEW_H
#define CHATVIEW_H
#include "../../tabs/tab_supervisor.h"
#include "../../interface/widgets/tabs/tab_supervisor.h"
#include "../user/user_list_widget.h"
#include <QAction>

View file

@ -2,11 +2,11 @@
#include "../dialogs/dlg_create_game.h"
#include "../dialogs/dlg_filter_games.h"
#include "../interface/widgets/tabs/tab_account.h"
#include "../interface/widgets/tabs/tab_game.h"
#include "../interface/widgets/tabs/tab_room.h"
#include "../interface/widgets/tabs/tab_supervisor.h"
#include "../interface/widgets/utility/get_text_with_max.h"
#include "../tabs/tab_account.h"
#include "../tabs/tab_game.h"
#include "../tabs/tab_room.h"
#include "../tabs/tab_supervisor.h"
#include "games_model.h"
#include "user/user_list_manager.h"

View file

@ -1,7 +1,7 @@
#include "games_model.h"
#include "../interface/pixel_map_generator.h"
#include "../tabs/tab_account.h"
#include "../interface/widgets/tabs/tab_account.h"
#include "user/user_list_manager.h"
#include "user/user_list_widget.h"

View file

@ -1,8 +1,8 @@
#include "user_context_menu.h"
#include "../../tabs/tab_account.h"
#include "../../tabs/tab_game.h"
#include "../../tabs/tab_supervisor.h"
#include "../../interface/widgets/tabs/tab_account.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../../interface/widgets/tabs/tab_supervisor.h"
#include "../chat_view/chat_view.h"
#include "../game_selector.h"
#include "user_info_box.h"

View file

@ -1,8 +1,8 @@
#include "user_list_widget.h"
#include "../../interface/pixel_map_generator.h"
#include "../../tabs/tab_account.h"
#include "../../tabs/tab_supervisor.h"
#include "../../interface/widgets/tabs/tab_account.h"
#include "../../interface/widgets/tabs/tab_supervisor.h"
#include "../game_selector.h"
#include "user_context_menu.h"
#include "user_list_manager.h"

View file

@ -0,0 +1,611 @@
#include "abstract_tab_deck_editor.h"
#include "../client/network/interfaces/deck_stats_interface.h"
#include "../client/network/interfaces/tapped_out_interface.h"
#include "../dialogs/dlg_load_deck.h"
#include "../dialogs/dlg_load_deck_from_clipboard.h"
#include "../dialogs/dlg_load_deck_from_website.h"
#include "../interface/card_picture_loader/card_picture_loader.h"
#include "../interface/pixel_map_generator.h"
#include "../interface/widgets/cards/card_info_frame_widget.h"
#include "tab_supervisor.h"
#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QCloseEvent>
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QFileDialog>
#include <QHeaderView>
#include <QLineEdit>
#include <QMenuBar>
#include <QMessageBox>
#include <QPrintPreviewDialog>
#include <QPrinter>
#include <QProcessEnvironment>
#include <QPushButton>
#include <QRegularExpression>
#include <QSplitter>
#include <QTextStream>
#include <QTreeView>
#include <QUrl>
#include <libcockatrice/card/card_database/card_database_manager.h>
#include <libcockatrice/card/card_database/model/card_database_model.h>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pb/command_deck_upload.pb.h>
#include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/settings/cache_settings.h>
#include <libcockatrice/utility/trice_limits.h>
AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
{
setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks);
databaseDisplayDockWidget = new DeckEditorDatabaseDisplayWidget(this);
deckDockWidget = new DeckEditorDeckDockWidget(this);
cardInfoDockWidget = new DeckEditorCardInfoDockWidget(this);
filterDockWidget = new DeckEditorFilterDockWidget(this);
printingSelectorDockWidget = new DeckEditorPrintingSelectorDockWidget(this);
connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, [this] {
printingSelectorDockWidget->setHidden(SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference());
});
connect(deckDockWidget, &DeckEditorDeckDockWidget::deckChanged, this, &AbstractTabDeckEditor::onDeckChanged);
connect(deckDockWidget, &DeckEditorDeckDockWidget::deckModified, this, &AbstractTabDeckEditor::onDeckModified);
connect(deckDockWidget, &DeckEditorDeckDockWidget::cardChanged, this, &AbstractTabDeckEditor::updateCard);
connect(this, &AbstractTabDeckEditor::decrementCard, deckDockWidget, &DeckEditorDeckDockWidget::actDecrementCard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, this,
&AbstractTabDeckEditor::updateCard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::addCardToMainDeck, this,
&AbstractTabDeckEditor::actAddCard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::addCardToSideboard, this,
&AbstractTabDeckEditor::actAddCardToSideboard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromMainDeck, this,
&AbstractTabDeckEditor::actDecrementCard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromSideboard, this,
&AbstractTabDeckEditor::actDecrementCardFromSideboard);
connect(filterDockWidget, &DeckEditorFilterDockWidget::clearAllDatabaseFilters, databaseDisplayDockWidget,
&DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters);
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&AbstractTabDeckEditor::refreshShortcuts);
}
void AbstractTabDeckEditor::updateCard(const ExactCard &card)
{
cardInfoDockWidget->updateCard(card);
printingSelectorDockWidget->printingSelector->setCard(card.getCardPtr(), DECK_ZONE_MAIN);
}
void AbstractTabDeckEditor::onDeckChanged()
{
}
void AbstractTabDeckEditor::onDeckModified()
{
setModified(!isBlankNewDeck());
deckMenu->setSaveStatus(!isBlankNewDeck());
}
void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneName)
{
if (!card)
return;
if (card.getInfo().getIsToken())
zoneName = DECK_ZONE_TOKENS;
QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName);
// recursiveExpand(newCardIndex);
deckDockWidget->deckView->clearSelection();
deckDockWidget->deckView->setCurrentIndex(newCardIndex);
setModified(true);
databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length());
}
void AbstractTabDeckEditor::actAddCard(const ExactCard &card)
{
if (QApplication::keyboardModifiers() & Qt::ControlModifier)
actAddCardToSideboard(card);
else
addCardHelper(card, DECK_ZONE_MAIN);
deckMenu->setSaveStatus(true);
}
void AbstractTabDeckEditor::actAddCardToSideboard(const ExactCard &card)
{
addCardHelper(card, DECK_ZONE_SIDE);
deckMenu->setSaveStatus(true);
}
void AbstractTabDeckEditor::actDecrementCard(const ExactCard &card)
{
emit decrementCard(card, DECK_ZONE_MAIN);
}
void AbstractTabDeckEditor::actDecrementCardFromSideboard(const ExactCard &card)
{
emit decrementCard(card, DECK_ZONE_SIDE);
}
void AbstractTabDeckEditor::actSwapCard(const ExactCard &card, const QString &zoneName)
{
QString providerId = card.getPrinting().getUuid();
QString collectorNumber = card.getPrinting().getProperty("num");
QModelIndex foundCard = deckDockWidget->deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber);
if (!foundCard.isValid()) {
foundCard = deckDockWidget->deckModel->findCard(card.getName(), zoneName);
}
deckDockWidget->swapCard(foundCard);
}
/**
* Opens the deck in this tab.
* @param deck The deck. Takes ownership of the object
*/
void AbstractTabDeckEditor::openDeck(DeckLoader *deck)
{
setDeck(deck);
if (!deck->getLastFileName().isEmpty()) {
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck->getLastFileName());
}
}
/**
* Sets the currently active deck for this tab
* @param _deck The deck. Takes ownership of the object
*/
void AbstractTabDeckEditor::setDeck(DeckLoader *_deck)
{
deckDockWidget->setDeck(_deck);
CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(getDeckList()->getCardRefList()));
setModified(false);
// If they load a deck, make the deck list appear
aDeckDockVisible->setChecked(true);
deckDockWidget->setVisible(aDeckDockVisible->isChecked());
}
DeckLoader *AbstractTabDeckEditor::getDeckList() const
{
return deckDockWidget->getDeckList();
}
void AbstractTabDeckEditor::setModified(bool _modified)
{
modified = _modified;
emit tabTextChanged(this, getTabText());
}
/**
* @brief Returns true if this tab is a blank newly opened tab, as if it was just created with the `New Deck` action.
*/
bool AbstractTabDeckEditor::isBlankNewDeck() const
{
DeckLoader *deck = getDeckList();
return !modified && deck->isBlankDeck() && deck->hasNotBeenLoaded();
}
void AbstractTabDeckEditor::actNewDeck()
{
auto deckOpenLocation = confirmOpen(false);
if (deckOpenLocation == CANCELLED) {
return;
}
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(nullptr);
return;
}
cleanDeckAndResetModified();
}
void AbstractTabDeckEditor::cleanDeckAndResetModified()
{
deckMenu->setSaveStatus(false);
deckDockWidget->cleanDeck();
setModified(false);
}
/**
* @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
*/
AbstractTabDeckEditor::DeckOpenLocation AbstractTabDeckEditor::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 *AbstractTabDeckEditor::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;
}
void AbstractTabDeckEditor::actLoadDeck()
{
auto deckOpenLocation = confirmOpen();
if (deckOpenLocation == CANCELLED) {
return;
}
DlgLoadDeck dialog(this);
if (!dialog.exec())
return;
QString fileName = dialog.selectedFiles().at(0);
openDeckFromFile(fileName, deckOpenLocation);
deckDockWidget->updateBannerCardComboBox();
}
void AbstractTabDeckEditor::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 AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation)
{
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
auto *l = new DeckLoader;
if (l->loadFromFile(fileName, fmt, true)) {
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(l);
l->deleteLater();
} else {
deckMenu->setSaveStatus(false);
openDeck(l);
}
} else {
l->deleteLater();
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName));
}
deckMenu->setSaveStatus(true);
}
bool AbstractTabDeckEditor::actSaveDeck()
{
DeckLoader *const deck = getDeckList();
if (deck->getLastRemoteDeckId() != -1) {
QString deckString = deck->writeToString_Native();
if (deckString.length() > MAX_FILE_LENGTH) {
QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck"));
return false;
}
Command_DeckUpload cmd;
cmd.set_deck_id(static_cast<google::protobuf::uint32>(deck->getLastRemoteDeckId()));
cmd.set_deck_list(deckString.toStdString());
PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd);
connect(pend, &PendingCommand::finished, this, &AbstractTabDeckEditor::saveDeckRemoteFinished);
tabSupervisor->getClient()->sendCommand(pend);
return true;
} else if (deck->getLastFileName().isEmpty())
return actSaveDeckAs();
else if (deck->saveToFile(deck->getLastFileName(), deck->getLastFileFormat())) {
setModified(false);
return true;
}
QMessageBox::critical(
this, tr("Error"),
tr("The deck could not be saved.\nPlease check that the directory is writable and try again."));
return false;
}
bool AbstractTabDeckEditor::actSaveDeckAs()
{
QFileDialog dialog(this, tr("Save deck"));
dialog.setDirectory(SettingsCache::instance().getDeckPath());
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setDefaultSuffix("cod");
dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS);
dialog.selectFile(getDeckList()->getName().trimmed());
if (!dialog.exec())
return false;
QString fileName = dialog.selectedFiles().at(0);
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
if (!getDeckList()->saveToFile(fileName, fmt)) {
QMessageBox::critical(
this, tr("Error"),
tr("The deck could not be saved.\nPlease check that the directory is writable and try again."));
return false;
}
setModified(false);
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName);
return true;
}
void AbstractTabDeckEditor::saveDeckRemoteFinished(const Response &response)
{
if (response.response_code() != Response::RespOk)
QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved."));
else
setModified(false);
}
void AbstractTabDeckEditor::actLoadDeckFromClipboard()
{
auto deckOpenLocation = confirmOpen();
if (deckOpenLocation == CANCELLED) {
return;
}
DlgLoadDeckFromClipboard dlg(this);
if (!dlg.exec())
return;
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(dlg.getDeckList());
} else {
setDeck(dlg.getDeckList());
setModified(true);
}
deckMenu->setSaveStatus(true);
}
void AbstractTabDeckEditor::editDeckInClipboard(bool annotated)
{
DlgEditDeckInClipboard dlg(*getDeckList(), annotated, this);
if (!dlg.exec())
return;
setDeck(dlg.getDeckList());
setModified(true);
deckMenu->setSaveStatus(true);
}
void AbstractTabDeckEditor::actEditDeckInClipboard()
{
editDeckInClipboard(true);
}
void AbstractTabDeckEditor::actEditDeckInClipboardRaw()
{
editDeckInClipboard(false);
}
void AbstractTabDeckEditor::actSaveDeckToClipboard()
{
getDeckList()->saveToClipboard(true, true);
}
void AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo()
{
getDeckList()->saveToClipboard(true, false);
}
void AbstractTabDeckEditor::actSaveDeckToClipboardRaw()
{
getDeckList()->saveToClipboard(false, true);
}
void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo()
{
getDeckList()->saveToClipboard(false, false);
}
void AbstractTabDeckEditor::actPrintDeck()
{
auto *dlg = new QPrintPreviewDialog(this);
connect(dlg, &QPrintPreviewDialog::paintRequested, deckDockWidget->deckModel, &DeckListModel::printDeckList);
dlg->exec();
}
void AbstractTabDeckEditor::actLoadDeckFromWebsite()
{
auto deckOpenLocation = confirmOpen();
if (deckOpenLocation == CANCELLED) {
return;
}
DlgLoadDeckFromWebsite dlg(this);
if (!dlg.exec())
return;
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(dlg.getDeck());
} else {
setDeck(dlg.getDeck());
setModified(true);
}
deckMenu->setSaveStatus(true);
}
void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website)
{
// check if deck is not null
if (DeckLoader *const deck = getDeckList()) {
// Get the decklist url string from the deck loader class.
QString decklistUrlString = deck->exportDeckToDecklist(website);
// Check to make sure the string isn't empty.
if (QString::compare(decklistUrlString, "", Qt::CaseInsensitive) == 0) {
// Show an error if the deck is empty, and return.
QMessageBox::critical(this, tr("Error"), tr("There are no cards in your deck to be exported"));
return;
}
// Encode the string recieved from the model to make sure all characters are encoded.
// first we put it into a qurl object
QUrl decklistUrl = QUrl(decklistUrlString);
// we get the correctly encoded url.
decklistUrlString = decklistUrl.toEncoded();
// We open the url in the user's default browser
QDesktopServices::openUrl(decklistUrlString);
} else {
// if there's no deck loader object, return an error
QMessageBox::critical(this, tr("Error"), tr("No deck was selected to be exported."));
}
}
/**
* Exports the deck to www.decklist.org (the old website)
*/
void AbstractTabDeckEditor::actExportDeckDecklist()
{
exportToDecklistWebsite(DeckLoader::DecklistOrg);
}
/**
* Exports the deck to www.decklist.xyz (the new website)
*/
void AbstractTabDeckEditor::actExportDeckDecklistXyz()
{
exportToDecklistWebsite(DeckLoader::DecklistXyz);
}
void AbstractTabDeckEditor::actAnalyzeDeckDeckstats()
{
auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(),
this); // it deletes itself when done
interface->analyzeDeck(getDeckList());
}
void AbstractTabDeckEditor::actAnalyzeDeckTappedout()
{
auto *interface = new TappedOutInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(),
this); // it deletes itself when done
interface->analyzeDeck(getDeckList());
}
void AbstractTabDeckEditor::filterTreeChanged(FilterTree *filterTree)
{
databaseDisplayDockWidget->setFilterTree(filterTree);
}
void AbstractTabDeckEditor::closeEvent(QCloseEvent *event)
{
emit deckEditorClosing(this);
event->accept();
}
// Method uses to sync docks state with menu items state
bool AbstractTabDeckEditor::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Close) {
if (o == cardInfoDockWidget) {
aCardInfoDockVisible->setChecked(false);
aCardInfoDockFloating->setEnabled(false);
} else if (o == deckDockWidget) {
aDeckDockVisible->setChecked(false);
aDeckDockFloating->setEnabled(false);
} else if (o == filterDockWidget) {
aFilterDockVisible->setChecked(false);
aFilterDockFloating->setEnabled(false);
} else if (o == printingSelectorDockWidget) {
aPrintingSelectorDockVisible->setChecked(false);
aPrintingSelectorDockFloating->setEnabled(false);
}
}
if (o == this && e->type() == QEvent::Hide) {
LayoutsSettings &layouts = SettingsCache::instance().layouts();
layouts.setDeckEditorLayoutState(saveState());
layouts.setDeckEditorGeometry(saveGeometry());
layouts.setDeckEditorCardSize(cardInfoDockWidget->size());
layouts.setDeckEditorFilterSize(filterDockWidget->size());
layouts.setDeckEditorDeckSize(deckDockWidget->size());
layouts.setDeckEditorPrintingSelectorSize(printingSelectorDockWidget->size());
}
return false;
}
bool AbstractTabDeckEditor::confirmClose()
{
if (modified) {
tabSupervisor->setCurrentWidget(this);
int ret = createSaveConfirmationWindow()->exec();
if (ret == QMessageBox::Save)
return actSaveDeck();
else if (ret == QMessageBox::Cancel)
return false;
}
return true;
}
bool AbstractTabDeckEditor::closeRequest()
{
if (!confirmClose()) {
return false;
}
return close();
}

View file

@ -0,0 +1,160 @@
#ifndef TAB_GENERIC_DECK_EDITOR_H
#define TAB_GENERIC_DECK_EDITOR_H
#include "../interface/widgets/deck_editor/deck_editor_card_info_dock_widget.h"
#include "../interface/widgets/deck_editor/deck_editor_database_display_widget.h"
#include "../interface/widgets/deck_editor/deck_editor_deck_dock_widget.h"
#include "../interface/widgets/deck_editor/deck_editor_filter_dock_widget.h"
#include "../interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.h"
#include "../interface/widgets/menus/deck_editor_menu.h"
#include "../interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h"
#include "tab.h"
class CardDatabaseModel;
class CardDatabaseDisplayModel;
class CardInfoFrameWidget;
class DeckLoader;
class DeckEditorMenu;
class DeckEditorCardInfoDockWidget;
class DeckEditorDatabaseDisplayWidget;
class DeckEditorDeckDockWidget;
class DeckEditorFilterDockWidget;
class DeckEditorPrintingSelectorDockWidget;
class DeckPreviewDeckTagsDisplayWidget;
class Response;
class FilterTree;
class FilterTreeModel;
class FilterBuilder;
class QTreeView;
class QTextEdit;
class QLabel;
class QComboBox;
class QGroupBox;
class QMessageBox;
class QHBoxLayout;
class QVBoxLayout;
class QPushButton;
class QDockWidget;
class QMenu;
class QAction;
class AbstractTabDeckEditor : public Tab
{
Q_OBJECT
friend class DeckEditorMenu;
public:
explicit AbstractTabDeckEditor(TabSupervisor *_tabSupervisor);
// UI and Navigation
virtual void createMenus() = 0;
[[nodiscard]] virtual QString getTabText() const override = 0;
bool confirmClose();
virtual void retranslateUi() override = 0;
// Deck Management
void openDeck(DeckLoader *deck);
DeckLoader *getDeckList() const;
void setModified(bool _windowModified);
// UI Elements
DeckEditorMenu *deckMenu;
DeckEditorDatabaseDisplayWidget *databaseDisplayDockWidget;
DeckEditorCardInfoDockWidget *cardInfoDockWidget;
DeckEditorDeckDockWidget *deckDockWidget;
DeckEditorFilterDockWidget *filterDockWidget;
DeckEditorPrintingSelectorDockWidget *printingSelectorDockWidget;
public slots:
virtual void onDeckChanged();
virtual void onDeckModified();
void updateCard(const ExactCard &card);
void actAddCard(const ExactCard &card);
void actAddCardToSideboard(const ExactCard &card);
void actDecrementCard(const ExactCard &card);
void actDecrementCardFromSideboard(const ExactCard &card);
void actOpenRecent(const QString &fileName);
void filterTreeChanged(FilterTree *filterTree);
bool closeRequest() override;
virtual void showPrintingSelector() = 0;
virtual void dockTopLevelChanged(bool topLevel) = 0;
signals:
void openDeckEditor(const DeckLoader *deckLoader);
void deckEditorClosing(AbstractTabDeckEditor *tab);
void decrementCard(const ExactCard &card, QString zoneName);
protected slots:
// Deck Operations
virtual void actNewDeck();
void cleanDeckAndResetModified();
virtual void actLoadDeck();
bool actSaveDeck();
virtual bool actSaveDeckAs();
virtual void actLoadDeckFromClipboard();
void actEditDeckInClipboard();
void actEditDeckInClipboardRaw();
void actSaveDeckToClipboard();
void actSaveDeckToClipboardNoSetInfo();
void actSaveDeckToClipboardRaw();
void actSaveDeckToClipboardRawNoSetInfo();
void actPrintDeck();
void actLoadDeckFromWebsite();
void actExportDeckDecklist();
void actExportDeckDecklistXyz();
void actAnalyzeDeckDeckstats();
void actAnalyzeDeckTappedout();
// Remote Save
void saveDeckRemoteFinished(const Response &r);
// UI Layout Management
virtual void loadLayout() = 0;
virtual void restartLayout() = 0;
virtual void freeDocksSize() = 0;
virtual void refreshShortcuts() = 0;
void closeEvent(QCloseEvent *event) override;
bool eventFilter(QObject *o, QEvent *e) override;
virtual void dockVisibleTriggered() = 0;
virtual void dockFloatingTriggered() = 0;
private:
virtual void setDeck(DeckLoader *_deck);
void editDeckInClipboard(bool annotated);
void exportToDecklistWebsite(DeckLoader::DecklistWebsite website);
protected:
/**
* @brief Enum for selecting deck open location
*/
enum DeckOpenLocation
{
CANCELLED,
SAME_TAB,
NEW_TAB
};
DeckOpenLocation confirmOpen(bool openInSameTabIfBlank = true);
QMessageBox *createSaveConfirmationWindow();
bool isBlankNewDeck() const;
// Helper functions for card actions
void addCardHelper(const ExactCard &card, QString zoneName);
void actSwapCard(const ExactCard &card, const QString &zoneName);
virtual void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation);
// UI Menu Elements
QMenu *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *printingSelectorDockMenu;
QAction *aResetLayout;
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating;
QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating;
bool modified = false;
};
#endif // TAB_GENERIC_DECK_EDITOR_H

View file

@ -0,0 +1,43 @@
#include "edhrec_api_response_archidekt_links.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
void EdhrecApiResponseArchidektLink::fromJson(const QJsonObject &json)
{
c = json.value("c").toString();
f = json.value("f").toInt(0);
q = json.value("q").toInt(0);
u = json.value("u").toString();
}
void EdhrecApiResponseArchidektLink::debugPrint() const
{
qDebug() << " C:" << c;
qDebug() << " F:" << f;
qDebug() << " Q:" << q;
qDebug() << " U:" << u;
}
void EdhrecCommanderApiResponseArchidektLinks::fromJson(const QJsonArray &json)
{
entries.clear();
for (const QJsonValue &value : json) {
if (value.isObject()) {
QJsonObject entryJson = value.toObject();
EdhrecApiResponseArchidektLink entry;
entry.fromJson(entryJson);
entries.append(entry);
}
}
}
void EdhrecCommanderApiResponseArchidektLinks::debugPrint() const
{
qDebug() << "Archidekt Entries:";
for (const auto &entry : entries) {
entry.debugPrint();
}
}

View file

@ -0,0 +1,40 @@
#ifndef ARCHIDEKTENTRY_H
#define ARCHIDEKTENTRY_H
#include <QDebug>
#include <QJsonObject>
#include <QString>
#include <QVector>
/**
* @class EdhrecApiResponseArchidektLink
* @ingroup ApiResponses
* @brief Represents a single Archidekt entry
*/
class EdhrecApiResponseArchidektLink
{
public:
QString c;
int f = 0;
int q = 0;
QString u;
void fromJson(const QJsonObject &json);
void debugPrint() const;
};
/**
* @class EdhrecCommanderApiResponseArchidektLinks
* @ingroup ApiResponses
* @brief Represents the Archidekt section as a list of entries
*/
class EdhrecCommanderApiResponseArchidektLinks
{
public:
QVector<EdhrecApiResponseArchidektLink> entries;
void fromJson(const QJsonArray &json);
void debugPrint() const;
};
#endif // ARCHIDEKTENTRY_H

View file

@ -0,0 +1,47 @@
#include "edhrec_average_deck_api_response.h"
#include <QDebug>
#include <QJsonArray>
void EdhrecAverageDeckApiResponse::fromJson(const QJsonObject &json)
{
// Parse the collapsed DeckStatistics
deckStats.fromJson(json);
// Parse Archidekt section
QJsonArray archidektJson = json.value("archidekt").toArray();
archidekt.fromJson(archidektJson);
// Parse other fields
similar = json.value("similar").toObject();
header = json.value("header").toString();
panels = json.value("panels").toObject();
description = json.value("description").toString();
QJsonObject containerJson = json.value("container").toObject();
container.fromJson(containerJson);
QJsonArray cardsJson = json.value("deck").toArray();
deck.fromJson(cardsJson);
}
void EdhrecAverageDeckApiResponse::debugPrint() const
{
qDebug() << "Deck Statistics:";
qDebug() << " Creature:" << deckStats.creature;
qDebug() << " Instant:" << deckStats.instant;
qDebug() << " Sorcery:" << deckStats.sorcery;
qDebug() << " Artifact:" << deckStats.artifact;
qDebug() << " Enchantment:" << deckStats.enchantment;
qDebug() << " Battle:" << deckStats.battle;
qDebug() << " Planeswalker:" << deckStats.planeswalker;
qDebug() << " Land:" << deckStats.land;
qDebug() << " Basic:" << deckStats.basic;
qDebug() << " Nonbasic:" << deckStats.nonbasic;
archidekt.debugPrint();
qDebug() << "Similar:" << similar;
qDebug() << "Header:" << header;
qDebug() << "Panels:" << panels;
qDebug() << "Description:" << description;
container.debugPrint();
}

View file

@ -0,0 +1,34 @@
#ifndef EDHREC_AVERAGE_DECK_API_RESPONSE_H
#define EDHREC_AVERAGE_DECK_API_RESPONSE_H
#include "../archidekt_links/edhrec_api_response_archidekt_links.h"
#include "../cards/edhrec_api_response_card_container.h"
#include "../commander/edhrec_commander_api_response_average_deck_statistics.h"
#include "edhrec_deck_api_response.h"
#include <QDebug>
#include <QJsonObject>
#include <QString>
/**
* @class EdhrecAverageDeckApiResponse
* @ingroup ApiResponses
* @brief Represents the main structure of the JSON
*/
class EdhrecAverageDeckApiResponse
{
public:
EdhrecCommanderApiResponseAverageDeckStatistics deckStats;
EdhrecCommanderApiResponseArchidektLinks archidekt;
QJsonObject similar;
QString header;
QJsonObject panels;
QString description;
EdhrecApiResponseCardContainer container;
EdhrecDeckApiResponse deck;
void fromJson(const QJsonObject &json);
void debugPrint() const;
};
#endif // EDHREC_AVERAGE_DECK_API_RESPONSE_H

View file

@ -0,0 +1,26 @@
#include "edhrec_deck_api_response.h"
#include <QApplication>
#include <QDebug>
#include <QJsonArray>
#include <QJsonObject>
#include <QMainWindow>
#include <libcockatrice/deck_list/deck_loader.h>
void EdhrecDeckApiResponse::fromJson(const QJsonArray &json)
{
QString deckList;
for (const QJsonValue &cardlistValue : json) {
deckList += cardlistValue.toString() + "\n";
}
deckLoader = new DeckLoader();
QTextStream stream(&deckList);
deckLoader->loadFromStream_Plain(stream, true);
}
void EdhrecDeckApiResponse::debugPrint() const
{
qDebug() << "Breadcrumb:";
}

View file

@ -0,0 +1,32 @@
/**
* @file edhrec_deck_api_response.h
* @ingroup ApiResponses
* @brief TODO: Document this.
*/
#ifndef EDHREC_DECK_API_RESPONSE_H
#define EDHREC_DECK_API_RESPONSE_H
#include <QDebug>
#include <QJsonArray>
#include <QJsonObject>
#include <QString>
#include <QVector>
#include <libcockatrice/deck_list/deck_loader.h>
class EdhrecDeckApiResponse
{
public:
// Constructor
EdhrecDeckApiResponse() = default;
// Parse deck-related data from JSON
void fromJson(const QJsonArray &json);
// Debug method for logging
void debugPrint() const;
DeckLoader *deckLoader;
};
#endif // EDHREC_DECK_API_RESPONSE_H

View file

@ -0,0 +1,31 @@
#include "edhrec_api_response_card_prices.h"
#include <QDebug>
void CardPrices::fromJson(const QJsonObject &json)
{
// Parse prices from various sources
cardhoarder = json.value("cardhoarder").toObject();
cardkingdom = json.value("cardkingdom").toObject();
cardmarket = json.value("cardmarket").toObject();
face2face = json.value("face2face").toObject();
manapool = json.value("manapool").toObject();
mtgstocks = json.value("mtgstocks").toObject();
scg = json.value("scg").toObject();
tcgl = json.value("tcgl").toObject();
tcgplayer = json.value("tcgplayer").toObject();
}
void CardPrices::debugPrint() const
{
qInfo() << "Card Prices:";
qInfo() << "Cardhoarder:" << cardhoarder;
qInfo() << "Cardkingdom:" << cardkingdom;
qInfo() << "Cardmarket:" << cardmarket;
qInfo() << "Face2Face:" << face2face;
qInfo() << "Manapool:" << manapool;
qInfo() << "Mtgstocks:" << mtgstocks;
qInfo() << "SCG:" << scg;
qInfo() << "TCGL:" << tcgl;
qInfo() << "Tcgplayer:" << tcgplayer;
}

View file

@ -0,0 +1,72 @@
/**
* @file edhrec_api_response_card_prices.h
* @ingroup ApiResponses
* @brief TODO: Document this.
*/
#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_PRICES_H
#define EDHREC_COMMANDER_API_RESPONSE_CARD_PRICES_H
#include <QJsonObject>
class CardPrices
{
public:
// Constructor
CardPrices() = default;
// Parse prices from JSON
void fromJson(const QJsonObject &json);
void debugPrint() const;
// Getter methods for card prices
const QJsonObject &getCardhoarder() const
{
return cardhoarder;
}
const QJsonObject &getCardkingdom() const
{
return cardkingdom;
}
const QJsonObject &getCardmarket() const
{
return cardmarket;
}
const QJsonObject &getFace2face() const
{
return face2face;
}
const QJsonObject &getManapool() const
{
return manapool;
}
const QJsonObject &getMtgstocks() const
{
return mtgstocks;
}
const QJsonObject &getScg() const
{
return scg;
}
const QJsonObject &getTcgl() const
{
return tcgl;
}
const QJsonObject &getTcgplayer() const
{
return tcgplayer;
}
private:
QJsonObject cardhoarder;
QJsonObject cardkingdom;
QJsonObject cardmarket;
QJsonObject face2face;
QJsonObject manapool;
QJsonObject mtgstocks;
QJsonObject scg;
QJsonObject tcgl;
QJsonObject tcgplayer;
};
#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_PRICES_H

View file

@ -0,0 +1,49 @@
#include "edhrec_api_response_card_container.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonObject>
void EdhrecApiResponseCardContainer::fromJson(const QJsonObject &json)
{
// Parse breadcrumb
QJsonArray breadcrumbArray = json.value("breadcrumb").toArray();
for (const QJsonValue &breadcrumbValue : breadcrumbArray) {
breadcrumb.push_back(breadcrumbValue.toObject());
}
description = json.value("description").toString();
QJsonObject jsonDict = json.value("json_dict").toObject();
card.fromJson(jsonDict.value("card").toObject());
QJsonArray cardlistsArray = jsonDict.value("cardlists").toArray();
for (const QJsonValue &cardlistValue : cardlistsArray) {
QJsonObject cardlistObj = cardlistValue.toObject();
QJsonArray cardviewsArray = cardlistObj.value("cardviews").toArray();
EdhrecApiResponseCardList cardView;
cardView.fromJson(cardlistValue.toObject());
cardlists.push_back(cardView);
}
keywords = json.value("keywords").toString();
title = json.value("title").toString();
}
void EdhrecApiResponseCardContainer::debugPrint() const
{
qDebug() << "Breadcrumb:";
for (const auto &breadcrumbEntry : breadcrumb) {
qDebug() << breadcrumbEntry;
}
qDebug() << "Description:" << description;
card.debugPrint();
qDebug() << "Cardlists:";
for (const auto &cardlist : cardlists) {
cardlist.debugPrint();
}
qDebug() << "Keywords:" << keywords;
qDebug() << "Title:" << title;
}

View file

@ -0,0 +1,66 @@
/**
* @file edhrec_api_response_card_container.h
* @ingroup ApiResponses
* @brief TODO: Document this.
*/
#ifndef CONTAINER_ENTRY_H
#define CONTAINER_ENTRY_H
#include "edhrec_api_response_card_list.h"
#include "edhrec_commander_api_response_commander_details.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonObject>
#include <QString>
#include <QVector>
class EdhrecApiResponseCardContainer
{
public:
// Constructor
EdhrecApiResponseCardContainer() = default;
// Parse deck-related data from JSON
void fromJson(const QJsonObject &json);
// Debug method for logging
void debugPrint() const;
// Getter methods for deck container
const QString &getDescription() const
{
return description;
}
const QVector<QJsonObject> &getBreadcrumb() const
{
return breadcrumb;
}
const EdhrecCommanderApiResponseCommanderDetails &getCommanderDetails() const
{
return card;
}
const QVector<EdhrecApiResponseCardList> &getCardlists() const
{
return cardlists;
}
const QString &getKeywords() const
{
return keywords;
}
const QString &getTitle() const
{
return title;
}
private:
QString description;
QVector<QJsonObject> breadcrumb;
EdhrecCommanderApiResponseCommanderDetails card;
QVector<EdhrecApiResponseCardList> cardlists;
QString keywords;
QString title;
};
#endif // CONTAINER_ENTRY_H

View file

@ -0,0 +1,36 @@
#include "edhrec_api_response_card_details.h"
#include <QDebug>
EdhrecApiResponseCardDetails::EdhrecApiResponseCardDetails()
: synergy(0.0), inclusion(0), numDecks(0), potentialDecks(0)
{
}
void EdhrecApiResponseCardDetails::fromJson(const QJsonObject &json)
{
// Parse the fields from the JSON object
name = json.value("name").toString();
sanitized = json.value("sanitized").toString();
sanitizedWo = json.value("sanitized_wo").toString();
url = json.value("url").toString();
synergy = json.value("synergy").toDouble(0.0);
inclusion = json.value("inclusion").toInt(0);
label = json.value("label").toString();
numDecks = json.value("num_decks").toInt(0);
potentialDecks = json.value("potential_decks").toInt(0);
}
void EdhrecApiResponseCardDetails::debugPrint() const
{
// Print out all the fields for debugging
qDebug() << "Name:" << name;
qDebug() << "Sanitized:" << sanitized;
qDebug() << "Sanitized Wo:" << sanitizedWo;
qDebug() << "URL:" << url;
qDebug() << "Synergy:" << synergy;
qDebug() << "Inclusion:" << inclusion;
qDebug() << "Label:" << label;
qDebug() << "Num Decks:" << numDecks;
qDebug() << "Potential Decks:" << potentialDecks;
}

View file

@ -0,0 +1,35 @@
/**
* @file edhrec_api_response_card_details.h
* @ingroup ApiResponses
* @brief TODO: Document this.
*/
#ifndef CARD_VIEW_H
#define CARD_VIEW_H
#include <QJsonObject>
#include <QString>
class EdhrecApiResponseCardDetails
{
public:
QString name;
QString sanitized;
QString sanitizedWo;
QString url;
double synergy;
int inclusion;
QString label;
int numDecks;
int potentialDecks;
EdhrecApiResponseCardDetails();
// Method to populate the object from a JSON object
void fromJson(const QJsonObject &json);
// Debug method to print out the data
void debugPrint() const;
};
#endif // CARD_VIEW_H

View file

@ -0,0 +1,33 @@
#include "edhrec_api_response_card_list.h"
#include <QDebug>
EdhrecApiResponseCardList::EdhrecApiResponseCardList()
{
}
void EdhrecApiResponseCardList::fromJson(const QJsonObject &json)
{
// Parse the header from the JSON object
header = json.value("header").toString();
// Parse the cardviews array and populate cardViews
QJsonArray cardviewsArray = json.value("cardviews").toArray();
for (const QJsonValue &value : cardviewsArray) {
QJsonObject cardviewObj = value.toObject();
EdhrecApiResponseCardDetails cardView;
cardView.fromJson(cardviewObj);
cardViews.append(cardView);
}
}
void EdhrecApiResponseCardList::debugPrint() const
{
// Print out the header
qDebug() << "Header:" << header;
// Print out all the CardView objects
for (const EdhrecApiResponseCardDetails &cardView : cardViews) {
cardView.debugPrint();
}
}

View file

@ -0,0 +1,33 @@
/**
* @file edhrec_api_response_card_list.h
* @ingroup ApiResponses
* @brief TODO: Document this.
*/
#ifndef CARD_LIST_H
#define CARD_LIST_H
#include "edhrec_api_response_card_details.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QList>
#include <QString>
class EdhrecApiResponseCardList
{
public:
QString header;
QList<EdhrecApiResponseCardDetails> cardViews;
// Default constructor
EdhrecApiResponseCardList();
// Method to populate the object from a JSON object
void fromJson(const QJsonObject &json);
// Debug method to print out the data
void debugPrint() const;
};
#endif // CARD_LIST_H

View file

@ -0,0 +1,90 @@
#include "edhrec_commander_api_response_commander_details.h"
#include <QDebug>
void EdhrecCommanderApiResponseCommanderDetails::fromJson(const QJsonObject &json)
{
// Parse card-related data
aetherhubUri = json.value("aetherhub_uri").toString();
archidektUri = json.value("archidekt_uri").toString();
cmc = json.value("cmc").toInt(0);
colorIdentity = json.value("color_identity").toArray();
combos = json.value("combos").toBool(false);
deckstatsUri = json.value("deckstats_uri").toString();
// Parse image URIs
QJsonArray imageUrisArray = json.value("image_uris").toArray();
for (const QJsonValue &imageValue : imageUrisArray) {
QJsonObject imageObject = imageValue.toObject();
imageUris.push_back(imageObject.value("normal").toString());
imageUris.push_back(imageObject.value("art_crop").toString());
}
inclusion = json.value("inclusion").toInt(0);
isCommander = json.value("is_commander").toBool(false);
label = json.value("label").toString();
layout = json.value("layout").toString();
legalCommander = json.value("legal_commander").toBool(false);
moxfieldUri = json.value("moxfield_uri").toString();
mtggoldfishUri = json.value("mtggoldfish_uri").toString();
name = json.value("name").toString();
names = json.value("names").toArray();
numDecks = json.value("num_decks").toInt(0);
potentialDecks = json.value("potential_decks").toInt(0);
precon = json.value("precon").toString();
// Parse prices
prices.fromJson(json.value("prices").toObject());
primaryType = json.value("primary_type").toString();
rarity = json.value("rarity").toString();
salt = json.value("salt").toDouble(0.0);
sanitized = json.value("sanitized").toString();
sanitizedWo = json.value("sanitized_wo").toString();
scryfallUri = json.value("scryfall_uri").toString();
spellbookUri = json.value("spellbook_uri").toString();
type = json.value("type").toString();
url = json.value("url").toString();
}
void EdhrecCommanderApiResponseCommanderDetails::debugPrint() const
{
qDebug() << "Card Data:";
qDebug() << "Aetherhub URI:" << aetherhubUri;
qDebug() << "Archidekt URI:" << archidektUri;
qDebug() << "CMC:" << cmc;
qDebug() << "Color Identity:" << colorIdentity;
qDebug() << "Combos:" << combos;
qDebug() << "Deckstats URI:" << deckstatsUri;
qDebug() << "Image URIs:";
for (const auto &uri : imageUris) {
qDebug() << uri;
}
qDebug() << "Inclusion:" << inclusion;
qDebug() << "Is Commander:" << isCommander;
qDebug() << "Label:" << label;
qDebug() << "Layout:" << layout;
qDebug() << "Legal Commander:" << legalCommander;
qDebug() << "Moxfield URI:" << moxfieldUri;
qDebug() << "MTGGoldfish URI:" << mtggoldfishUri;
qDebug() << "Name:" << name;
qDebug() << "Names:" << names;
qDebug() << "Number of Decks:" << numDecks;
qDebug() << "Potential Decks:" << potentialDecks;
qDebug() << "Precon:" << precon;
// Print the prices using the debugPrint method from CardPrices
prices.debugPrint();
qDebug() << "Primary Type:" << primaryType;
qDebug() << "Rarity:" << rarity;
qDebug() << "Salt:" << salt;
qDebug() << "Sanitized:" << sanitized;
qDebug() << "Sanitized WO:" << sanitizedWo;
qDebug() << "Scryfall URI:" << scryfallUri;
qDebug() << "Spellbook URI:" << spellbookUri;
qDebug() << "Type:" << type;
qDebug() << "URL:" << url;
}

View file

@ -0,0 +1,179 @@
/**
* @file edhrec_commander_api_response_commander_details.h
* @ingroup ApiResponses
* @brief TODO: Document this.
*/
#ifndef EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H
#define EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H
#include "../card_prices/edhrec_api_response_card_prices.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QString>
#include <QVector>
class EdhrecCommanderApiResponseCommanderDetails
{
public:
// Constructor
EdhrecCommanderApiResponseCommanderDetails() = default;
// Parse card-related data from JSON
void fromJson(const QJsonObject &json);
// Debug method for logging
void debugPrint() const;
// Getters for the card data
const QString &getAetherhubUri() const
{
return aetherhubUri;
}
const QString &getArchidektUri() const
{
return archidektUri;
}
int getCmc() const
{
return cmc;
}
const QJsonArray &getColorIdentity() const
{
return colorIdentity;
}
bool isCombos() const
{
return combos;
}
const QString &getDeckstatsUri() const
{
return deckstatsUri;
}
const QVector<QString> &getImageUris() const
{
return imageUris;
}
int getInclusion() const
{
return inclusion;
}
bool getIsCommander() const
{
return isCommander;
}
const QString &getLabel() const
{
return label;
}
const QString &getLayout() const
{
return layout;
}
bool getLegalCommander() const
{
return legalCommander;
}
const QString &getMoxfieldUri() const
{
return moxfieldUri;
}
const QString &getMtggoldfishUri() const
{
return mtggoldfishUri;
}
const QString &getName() const
{
return name;
}
const QJsonArray &getNames() const
{
return names;
}
int getNumDecks() const
{
return numDecks;
}
int getPotentialDecks() const
{
return potentialDecks;
}
const QString &getPrecon() const
{
return precon;
}
const CardPrices &getPrices() const
{
return prices;
}
const QString &getPrimaryType() const
{
return primaryType;
}
const QString &getRarity() const
{
return rarity;
}
double getSalt() const
{
return salt;
}
const QString &getSanitized() const
{
return sanitized;
}
const QString &getSanitizedWo() const
{
return sanitizedWo;
}
const QString &getScryfallUri() const
{
return scryfallUri;
}
const QString &getSpellbookUri() const
{
return spellbookUri;
}
const QString &getType() const
{
return type;
}
const QString &getUrl() const
{
return url;
}
private:
QString aetherhubUri;
QString archidektUri;
int cmc = 0;
QJsonArray colorIdentity;
bool combos = false;
QString deckstatsUri;
QVector<QString> imageUris;
int inclusion = 0;
bool isCommander = false;
QString label;
QString layout;
bool legalCommander = false;
QString moxfieldUri;
QString mtggoldfishUri;
QString name;
QJsonArray names;
int numDecks = 0;
int potentialDecks = 0;
QString precon;
CardPrices prices;
QString primaryType;
QString rarity;
double salt = 0.0;
QString sanitized;
QString sanitizedWo;
QString scryfallUri;
QString spellbookUri;
QString type;
QString url;
};
#endif // EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H

View file

@ -0,0 +1,45 @@
#include "edhrec_commander_api_response.h"
#include <QDebug>
#include <QJsonArray>
void EdhrecCommanderApiResponse::fromJson(const QJsonObject &json)
{
// Parse the collapsed DeckStatistics
deckStats.fromJson(json);
// Parse Archidekt section
QJsonArray archidektJson = json.value("archidekt").toArray();
archidekt.fromJson(archidektJson);
// Parse other fields
similar = json.value("similar").toObject();
header = json.value("header").toString();
panels = json.value("panels").toObject();
description = json.value("description").toString();
QJsonObject containerJson = json.value("container").toObject();
container.fromJson(containerJson);
}
void EdhrecCommanderApiResponse::debugPrint() const
{
qDebug() << "Deck Statistics:";
qDebug() << " Creature:" << deckStats.creature;
qDebug() << " Instant:" << deckStats.instant;
qDebug() << " Sorcery:" << deckStats.sorcery;
qDebug() << " Artifact:" << deckStats.artifact;
qDebug() << " Enchantment:" << deckStats.enchantment;
qDebug() << " Battle:" << deckStats.battle;
qDebug() << " Planeswalker:" << deckStats.planeswalker;
qDebug() << " Land:" << deckStats.land;
qDebug() << " Basic:" << deckStats.basic;
qDebug() << " Nonbasic:" << deckStats.nonbasic;
archidekt.debugPrint();
qDebug() << "Similar:" << similar;
qDebug() << "Header:" << header;
qDebug() << "Panels:" << panels;
qDebug() << "Description:" << description;
container.debugPrint();
}

View file

@ -0,0 +1,32 @@
#ifndef DECKDATA_H
#define DECKDATA_H
#include "../archidekt_links/edhrec_api_response_archidekt_links.h"
#include "../cards/edhrec_api_response_card_container.h"
#include "edhrec_commander_api_response_average_deck_statistics.h"
#include <QDebug>
#include <QJsonObject>
#include <QString>
/**
* @class EdhrecCommanderApiResponse
* @ingroup ApiResponses
* @brief Represents the main structure of the JSON
*/
class EdhrecCommanderApiResponse
{
public:
EdhrecCommanderApiResponseAverageDeckStatistics deckStats;
EdhrecCommanderApiResponseArchidektLinks archidekt;
QJsonObject similar;
QString header;
QJsonObject panels;
QString description;
EdhrecApiResponseCardContainer container;
void fromJson(const QJsonObject &json);
void debugPrint() const;
};
#endif // DECKDATA_H

View file

@ -0,0 +1,15 @@
#include "edhrec_commander_api_response_average_deck_statistics.h"
void EdhrecCommanderApiResponseAverageDeckStatistics::fromJson(const QJsonObject &json)
{
creature = json.value("creature").toInt(0);
instant = json.value("instant").toInt(0);
sorcery = json.value("sorcery").toInt(0);
artifact = json.value("artifact").toInt(0);
enchantment = json.value("enchantment").toInt(0);
battle = json.value("battle").toInt(0);
planeswalker = json.value("planeswalker").toInt(0);
land = json.value("land").toInt(0);
basic = json.value("basic").toInt(0);
nonbasic = json.value("nonbasic").toInt(0);
}

View file

@ -0,0 +1,26 @@
#ifndef AVERAGE_DECK_STATISTICS_H
#define AVERAGE_DECK_STATISTICS_H
#include <QJsonObject>
/**
* @struct EdhrecCommanderApiResponseAverageDeckStatistics
* @ingroup ApiResponses
* @brief Represents the typical deck statistics (collapsed section)
*/
struct EdhrecCommanderApiResponseAverageDeckStatistics
{
int creature = 0;
int instant = 0;
int sorcery = 0;
int artifact = 0;
int enchantment = 0;
int battle = 0;
int planeswalker = 0;
int land = 0;
int basic = 0;
int nonbasic = 0;
void fromJson(const QJsonObject &json);
};
#endif // AVERAGE_DECK_STATISTICS_H

View file

@ -0,0 +1,19 @@
#include "edhrec_top_cards_api_response.h"
#include <QDebug>
#include <QJsonArray>
void EdhrecTopCardsApiResponse::fromJson(const QJsonObject &json)
{
header = json.value("header").toString();
description = json.value("description").toString();
QJsonObject containerJson = json.value("container").toObject();
container.fromJson(containerJson);
}
void EdhrecTopCardsApiResponse::debugPrint() const
{
qDebug() << "Header:" << header;
qDebug() << "Description:" << description;
container.debugPrint();
}

View file

@ -0,0 +1,26 @@
/**
* @file edhrec_top_cards_api_response.h
* @ingroup ApiResponses
* @brief TODO: Document this.
*/
#ifndef EDHREC_TOP_CARDS_API_RESPONSE_H
#define EDHREC_TOP_CARDS_API_RESPONSE_H
#include "../cards/edhrec_api_response_card_container.h"
#include <QDebug>
#include <QString>
class EdhrecTopCardsApiResponse
{
public:
QString header;
QString description;
EdhrecApiResponseCardContainer container;
void fromJson(const QJsonObject &json);
void debugPrint() const;
};
#endif // EDHREC_TOP_CARDS_API_RESPONSE_H

View file

@ -0,0 +1,19 @@
#include "edhrec_top_commanders_api_response.h"
#include <QDebug>
#include <QJsonArray>
void EdhrecTopCommandersApiResponse::fromJson(const QJsonObject &json)
{
header = json.value("header").toString();
description = json.value("description").toString();
QJsonObject containerJson = json.value("container").toObject();
container.fromJson(containerJson);
}
void EdhrecTopCommandersApiResponse::debugPrint() const
{
qDebug() << "Header:" << header;
qDebug() << "Description:" << description;
container.debugPrint();
}

View file

@ -0,0 +1,26 @@
/**
* @file edhrec_top_commanders_api_response.h
* @ingroup ApiResponses
* @brief TODO: Document this.
*/
#ifndef EDHREC_TOP_COMMANDERS_API_RESPONSE_H
#define EDHREC_TOP_COMMANDERS_API_RESPONSE_H
#include "../cards/edhrec_api_response_card_container.h"
#include <QDebug>
#include <QString>
class EdhrecTopCommandersApiResponse
{
public:
QString header;
QString description;
EdhrecApiResponseCardContainer container;
void fromJson(const QJsonObject &json);
void debugPrint() const;
};
#endif // EDHREC_TOP_COMMANDERS_API_RESPONSE_H

View file

@ -0,0 +1,19 @@
#include "edhrec_top_tags_api_response.h"
#include <QDebug>
#include <QJsonArray>
void EdhrecTopTagsApiResponse::fromJson(const QJsonObject &json)
{
header = json.value("header").toString();
description = json.value("description").toString();
QJsonObject containerJson = json.value("container").toObject();
container.fromJson(containerJson);
}
void EdhrecTopTagsApiResponse::debugPrint() const
{
qDebug() << "Header:" << header;
qDebug() << "Description:" << description;
container.debugPrint();
}

View file

@ -0,0 +1,26 @@
/**
* @file edhrec_top_tags_api_response.h
* @ingroup ApiResponses
* @brief TODO: Document this.
*/
#ifndef EDHREC_TOP_TAGS_API_RESPONSE_H
#define EDHREC_TOP_TAGS_API_RESPONSE_H
#include "../cards/edhrec_api_response_card_container.h"
#include <QDebug>
#include <QString>
class EdhrecTopTagsApiResponse
{
public:
QString header;
QString description;
EdhrecApiResponseCardContainer container;
void fromJson(const QJsonObject &json);
void debugPrint() const;
};
#endif // EDHREC_TOP_TAGS_API_RESPONSE_H

View file

@ -0,0 +1,66 @@
#include "edhrec_api_response_card_prices_display_widget.h"
EdhrecApiResponseCardPricesDisplayWidget::EdhrecApiResponseCardPricesDisplayWidget(QWidget *parent,
const CardPrices &_cardPrices)
: QWidget(parent), cardPrices(_cardPrices)
{
layout = new QGridLayout(this);
setLayout(layout);
cardHoarderLabel = new QLabel(this);
cardHoarderPrice = new QLabel(QString::number(cardPrices.getCardhoarder().value("price").toDouble()), this);
cardKingdomLabel = new QLabel(this);
cardKingdomPrice = new QLabel(QString::number(cardPrices.getCardkingdom().value("price").toDouble()), this);
cardMarketLabel = new QLabel(this);
cardMarketPrice = new QLabel(QString::number(cardPrices.getCardmarket().value("price").toDouble()), this);
face2faceLabel = new QLabel(this);
face2facePrice = new QLabel(QString::number(cardPrices.getFace2face().value("price").toDouble()), this);
manaPoolLabel = new QLabel(this);
manaPoolPrice = new QLabel(QString::number(cardPrices.getManapool().value("price").toDouble()), this);
mtgStocksLabel = new QLabel(this);
mtgStocksPrice = new QLabel(QString::number(cardPrices.getMtgstocks().value("price").toDouble()), this);
scgLabel = new QLabel(this);
scgPrice = new QLabel(QString::number(cardPrices.getScg().value("price").toDouble()), this);
tcglLabel = new QLabel(this);
tcglPrice = new QLabel(QString::number(cardPrices.getTcgl().value("price").toDouble()), this);
tcgplayerLabel = new QLabel(this);
tcgplayerPrice = new QLabel(QString::number(cardPrices.getTcgplayer().value("price").toDouble()), this);
layout->addWidget(cardHoarderLabel, 0, 0);
layout->addWidget(cardHoarderPrice, 0, 1);
layout->addWidget(cardKingdomLabel, 0, 2);
layout->addWidget(cardKingdomPrice, 0, 3);
layout->addWidget(cardMarketLabel, 1, 0);
layout->addWidget(cardMarketPrice, 1, 1);
layout->addWidget(face2faceLabel, 1, 2);
layout->addWidget(face2facePrice, 1, 3);
layout->addWidget(manaPoolLabel, 2, 0);
layout->addWidget(manaPoolPrice, 2, 1);
layout->addWidget(mtgStocksLabel, 2, 2);
layout->addWidget(mtgStocksPrice, 2, 3);
layout->addWidget(scgLabel, 3, 0);
layout->addWidget(scgPrice, 3, 1);
layout->addWidget(tcglLabel, 3, 2);
layout->addWidget(tcglPrice, 3, 3);
layout->addWidget(tcgplayerLabel, 4, 0);
layout->addWidget(tcgplayerPrice, 4, 1);
retranslateUi();
}
void EdhrecApiResponseCardPricesDisplayWidget::retranslateUi()
{
cardHoarderLabel->setText(tr("Card Hoarder"));
cardKingdomLabel->setText(tr("Card Kingdom"));
cardMarketLabel->setText(tr("Card Market"));
face2faceLabel->setText(tr("Face 2-Face"));
manaPoolLabel->setText(tr("Mana Pool"));
mtgStocksLabel->setText(tr("MTG Stocks"));
scgLabel->setText(tr("Scg"));
tcglLabel->setText(tr("Tcgl"));
tcgplayerLabel->setText(tr("Tcgplayer"));
}

View file

@ -0,0 +1,48 @@
/**
* @file edhrec_api_response_card_prices_display_widget.h
* @ingroup ApiResponseDisplayWidgets
* @brief TODO: Document this.
*/
#ifndef EDHREC_API_RESPONSE_CARD_PRICES_DISPLAY_WIDGET_H
#define EDHREC_API_RESPONSE_CARD_PRICES_DISPLAY_WIDGET_H
#include "../../api_response/card_prices/edhrec_api_response_card_prices.h"
#include <QGridLayout>
#include <QLabel>
#include <QWidget>
class EdhrecApiResponseCardPricesDisplayWidget : public QWidget
{
Q_OBJECT
public:
EdhrecApiResponseCardPricesDisplayWidget(QWidget *parent, const CardPrices &cardPrices);
public slots:
void retranslateUi();
private:
CardPrices cardPrices;
QGridLayout *layout;
QLabel *cardHoarderLabel;
QLabel *cardHoarderPrice;
QLabel *cardKingdomLabel;
QLabel *cardKingdomPrice;
QLabel *cardMarketLabel;
QLabel *cardMarketPrice;
QLabel *face2faceLabel;
QLabel *face2facePrice;
QLabel *manaPoolLabel;
QLabel *manaPoolPrice;
QLabel *mtgStocksLabel;
QLabel *mtgStocksPrice;
QLabel *scgLabel;
QLabel *scgPrice;
QLabel *tcglLabel;
QLabel *tcglPrice;
QLabel *tcgplayerLabel;
QLabel *tcgplayerPrice;
};
#endif // EDHREC_API_RESPONSE_CARD_PRICES_DISPLAY_WIDGET_H

View file

@ -0,0 +1,55 @@
#include "edhrec_api_response_card_details_display_widget.h"
#include "../../tab_edhrec_main.h"
#include <libcockatrice/card/card_database/card_database_manager.h>
EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWidget(
QWidget *parent,
const EdhrecApiResponseCardDetails &_toDisplay)
: QWidget(parent), toDisplay(_toDisplay)
{
layout = new QVBoxLayout(this);
setLayout(layout);
cardPictureWidget = new CardInfoPictureWidget(this);
cardPictureWidget->setCard(CardDatabaseManager::query()->guessCard({toDisplay.sanitized}));
nameLabel = new QLabel(this);
nameLabel->setText(toDisplay.name);
nameLabel->setAlignment(Qt::AlignHCenter);
inclusionDisplayWidget = new EdhrecApiResponseCardInclusionDisplayWidget(this, toDisplay);
synergyDisplayWidget = new EdhrecApiResponseCardSynergyDisplayWidget(this, toDisplay);
layout->addWidget(nameLabel);
layout->addWidget(cardPictureWidget);
layout->addWidget(inclusionDisplayWidget);
layout->addWidget(synergyDisplayWidget);
QWidget *currentParent = parentWidget();
TabEdhRecMain *parentTab = nullptr;
while (currentParent) {
if ((parentTab = qobject_cast<TabEdhRecMain *>(currentParent))) {
break;
}
currentParent = currentParent->parentWidget();
}
if (parentTab) {
cardPictureWidget->setScaleFactor(parentTab->getCardSizeSlider()->getSlider()->value());
connect(cardPictureWidget, &CardInfoPictureWidget::cardClicked, this,
&EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation);
connect(parentTab->getCardSizeSlider()->getSlider(), &QSlider::valueChanged, cardPictureWidget,
&CardInfoPictureWidget::setScaleFactor);
connect(this, &EdhrecApiResponseCardDetailsDisplayWidget::requestUrl, parentTab,
&TabEdhRecMain::actNavigatePage);
}
}
void EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation()
{
emit requestUrl(toDisplay.url);
}

View file

@ -0,0 +1,38 @@
/**
* @file edhrec_api_response_card_details_display_widget.h
* @ingroup ApiResponseDisplayWidgets
* @brief TODO: Document this.
*/
#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H
#define EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H
#include "../../../../../cards/card_info_picture_widget.h"
#include "../../api_response/cards/edhrec_api_response_card_details.h"
#include "edhrec_api_response_card_inclusion_display_widget.h"
#include "edhrec_api_response_card_synergy_display_widget.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QWidget>
class EdhrecApiResponseCardDetailsDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit EdhrecApiResponseCardDetailsDisplayWidget(QWidget *parent, const EdhrecApiResponseCardDetails &_toDisplay);
public slots:
void actRequestPageNavigation();
signals:
void requestUrl(QString url);
private:
EdhrecApiResponseCardDetails toDisplay;
QVBoxLayout *layout;
CardInfoPictureWidget *cardPictureWidget;
QLabel *nameLabel;
EdhrecApiResponseCardInclusionDisplayWidget *inclusionDisplayWidget;
EdhrecApiResponseCardSynergyDisplayWidget *synergyDisplayWidget;
};
#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H

View file

@ -0,0 +1,41 @@
#include "edhrec_api_response_card_inclusion_display_widget.h"
EdhrecApiResponseCardInclusionDisplayWidget::EdhrecApiResponseCardInclusionDisplayWidget(
QWidget *parent,
const EdhrecApiResponseCardDetails &_toDisplay)
: QWidget(parent), toDisplay(_toDisplay)
{
layout = new QVBoxLayout(this);
setLayout(layout);
commanderLabel = new QLabel(this);
commanderLabel->setAlignment(Qt::AlignCenter);
amountLabel = new QLabel(this);
amountLabel->setAlignment(Qt::AlignCenter);
inclusionLabel = new QLabel(this);
inclusionLabel->setAlignment(Qt::AlignCenter);
percentBarWidget = new PercentBarWidget(this, toDisplay.inclusion / (toDisplay.potentialDecks / 100.0));
if (toDisplay.inclusion != 0 && toDisplay.potentialDecks != 0) {
layout->addWidget(amountLabel);
layout->addWidget(inclusionLabel);
layout->addWidget(percentBarWidget);
commanderLabel->hide();
} else {
amountLabel->hide();
inclusionLabel->hide();
percentBarWidget->hide();
layout->addWidget(commanderLabel);
}
retranslateUi();
}
void EdhrecApiResponseCardInclusionDisplayWidget::retranslateUi()
{
commanderLabel->setText(toDisplay.label);
amountLabel->setText(tr("In %1 decks").arg(QString::number(toDisplay.inclusion)));
inclusionLabel->setText(tr("%1% of %2 decks")
.arg(QString::number(toDisplay.inclusion / (toDisplay.potentialDecks / 100.0), 'f', 2),
QString::number(toDisplay.potentialDecks)));
}

View file

@ -0,0 +1,33 @@
/**
* @file edhrec_api_response_card_inclusion_display_widget.h
* @ingroup ApiResponseDisplayWidgets
* @brief TODO: Document this.
*/
#ifndef EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H
#define EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H
#include "../../../../../general/display/percent_bar_widget.h"
#include "../../api_response/cards/edhrec_api_response_card_details.h"
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
class EdhrecApiResponseCardInclusionDisplayWidget : public QWidget
{
Q_OBJECT
public:
EdhrecApiResponseCardInclusionDisplayWidget(QWidget *parent, const EdhrecApiResponseCardDetails &_toDisplay);
void retranslateUi();
private:
QVBoxLayout *layout;
EdhrecApiResponseCardDetails toDisplay;
QLabel *commanderLabel;
QLabel *amountLabel;
QLabel *inclusionLabel;
PercentBarWidget *percentBarWidget;
};
#endif // EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H

View file

@ -0,0 +1,33 @@
#include "edhrec_api_response_card_list_display_widget.h"
#include "../../../../../general/display/banner_widget.h"
#include "edhrec_api_response_card_details_display_widget.h"
#include <QLabel>
EdhrecApiResponseCardListDisplayWidget::EdhrecApiResponseCardListDisplayWidget(QWidget *parent,
EdhrecApiResponseCardList toDisplay)
: QWidget(parent)
{
layout = new QVBoxLayout(this);
setLayout(layout);
header = new BannerWidget(this, toDisplay.header);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
header->setBuddy(flowWidget);
foreach (EdhrecApiResponseCardDetails card_detail, toDisplay.cardViews) {
auto widget = new EdhrecApiResponseCardDetailsDisplayWidget(flowWidget, card_detail);
flowWidget->addWidget(widget);
}
layout->addWidget(header);
layout->addWidget(flowWidget);
}
void EdhrecApiResponseCardListDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
qDebug() << event->size();
}

View file

@ -0,0 +1,35 @@
/**
* @file edhrec_api_response_card_list_display_widget.h
* @ingroup ApiResponseDisplayWidgets
* @brief TODO: Document this.
*/
#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H
#define EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H
#include "../../../../../general/display/banner_widget.h"
#include "../../../../../general/layout_containers/flow_widget.h"
#include "../../api_response/cards/edhrec_api_response_card_list.h"
#include <QResizeEvent>
#include <QVBoxLayout>
#include <QWidget>
class EdhrecApiResponseCardListDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit EdhrecApiResponseCardListDisplayWidget(QWidget *parent, EdhrecApiResponseCardList toDisplay);
void resizeEvent(QResizeEvent *event) override;
[[nodiscard]] QString getBannerText() const
{
return header->getText();
};
private:
QVBoxLayout *layout;
BannerWidget *header;
FlowWidget *flowWidget;
};
#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H

View file

@ -0,0 +1,28 @@
#include "edhrec_api_response_card_synergy_display_widget.h"
EdhrecApiResponseCardSynergyDisplayWidget::EdhrecApiResponseCardSynergyDisplayWidget(
QWidget *parent,
const EdhrecApiResponseCardDetails &_toDisplay)
: QWidget(parent), toDisplay(_toDisplay)
{
layout = new QVBoxLayout(this);
setLayout(layout);
label = new QLabel(this);
label->setAlignment(Qt::AlignCenter);
percentBarWidget = new PercentBarWidget(this, toDisplay.synergy * 100.0);
if (toDisplay.synergy != 0) {
layout->addWidget(label);
layout->addWidget(percentBarWidget);
} else {
hide();
}
retranslateUi();
}
void EdhrecApiResponseCardSynergyDisplayWidget::retranslateUi()
{
label->setText(tr("%1% Synergy").arg(QString::number(toDisplay.synergy * 100.0, 'f', 1)));
}

View file

@ -0,0 +1,31 @@
/**
* @file edhrec_api_response_card_synergy_display_widget.h
* @ingroup ApiResponseDisplayWidgets
* @brief TODO: Document this.
*/
#ifndef EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H
#define EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H
#include "../../../../../general/display/percent_bar_widget.h"
#include "../../api_response/cards/edhrec_api_response_card_details.h"
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
class EdhrecApiResponseCardSynergyDisplayWidget : public QWidget
{
Q_OBJECT
public:
EdhrecApiResponseCardSynergyDisplayWidget(QWidget *parent, const EdhrecApiResponseCardDetails &_toDisplay);
void retranslateUi();
private:
QVBoxLayout *layout;
EdhrecApiResponseCardDetails toDisplay;
QLabel *label;
PercentBarWidget *percentBarWidget;
};
#endif // EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H

View file

@ -0,0 +1,61 @@
#include "edhrec_api_response_commander_details_display_widget.h"
#include "../../../../../cards/card_info_picture_widget.h"
#include "../../tab_edhrec_main.h"
#include "../card_prices/edhrec_api_response_card_prices_display_widget.h"
#include <libcockatrice/card/card_database/card_database_manager.h>
EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCommanderDetailsDisplayWidget(
QWidget *parent,
const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails,
QString baseUrl)
: QWidget(parent), commanderDetails(_commanderDetails)
{
layout = new QVBoxLayout(this);
setLayout(layout);
commanderPicture = new CardInfoPictureWidget(this);
commanderPicture->setCard(CardDatabaseManager::query()->getCard({commanderDetails.getName()}));
QWidget *currentParent = parentWidget();
TabEdhRecMain *parentTab = nullptr;
while (currentParent) {
if ((parentTab = qobject_cast<TabEdhRecMain *>(currentParent))) {
break;
}
currentParent = currentParent->parentWidget();
}
if (parentTab) {
connect(parentTab->getCardSizeSlider()->getSlider(), &QSlider::valueChanged, commanderPicture,
&CardInfoPictureWidget::setScaleFactor);
commanderPicture->setScaleFactor(parentTab->getCardSizeSlider()->getSlider()->value());
}
commanderDetails.debugPrint();
label = new QLabel(this);
label->setAlignment(Qt::AlignCenter);
salt = new QLabel(this);
salt->setAlignment(Qt::AlignCenter);
cardPricesDisplayWidget = new EdhrecApiResponseCardPricesDisplayWidget(this, commanderDetails.getPrices());
navigationWidget = new EdhrecCommanderApiResponseNavigationWidget(this, commanderDetails, baseUrl);
layout->addWidget(commanderPicture);
layout->addWidget(label);
layout->addWidget(salt);
layout->addWidget(cardPricesDisplayWidget);
layout->addWidget(navigationWidget);
retranslateUi();
}
void EdhrecCommanderResponseCommanderDetailsDisplayWidget::retranslateUi()
{
label->setText(commanderDetails.getLabel());
salt->setText(tr("Salt: ") + QString::number(commanderDetails.getSalt()));
}

View file

@ -0,0 +1,40 @@
/**
* @file edhrec_api_response_commander_details_display_widget.h
* @ingroup ApiResponseDisplayWidgets
* @brief TODO: Document this.
*/
#ifndef EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H
#define EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H
#include "../../../../../cards/card_info_picture_widget.h"
#include "../../api_response/cards/edhrec_commander_api_response_commander_details.h"
#include "../card_prices/edhrec_api_response_card_prices_display_widget.h"
#include "edhrec_commander_api_response_navigation_widget.h"
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
class EdhrecCommanderApiResponseNavigationWidget;
class EdhrecCommanderResponseCommanderDetailsDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit EdhrecCommanderResponseCommanderDetailsDisplayWidget(
QWidget *parent,
const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails,
QString baseUrl);
void retranslateUi();
private:
EdhrecCommanderApiResponseCommanderDetails commanderDetails;
QVBoxLayout *layout;
CardInfoPictureWidget *commanderPicture;
QLabel *label;
QLabel *salt;
EdhrecApiResponseCardPricesDisplayWidget *cardPricesDisplayWidget;
EdhrecCommanderApiResponseNavigationWidget *navigationWidget;
};
#endif // EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H

View file

@ -0,0 +1,103 @@
#include "edhrec_commander_api_response_display_widget.h"
#include "../../../../../cards/card_info_picture_widget.h"
#include "../../api_response/commander/edhrec_commander_api_response.h"
#include "../cards/edhrec_api_response_card_list_display_widget.h"
#include "edhrec_api_response_commander_details_display_widget.h"
#include <QListView>
#include <QResizeEvent>
#include <QScrollArea>
#include <QSplitter>
#include <QStringListModel>
EdhrecCommanderApiResponseDisplayWidget::EdhrecCommanderApiResponseDisplayWidget(QWidget *parent,
EdhrecCommanderApiResponse response,
QString baseUrl)
: QWidget(parent)
{
layout = new QHBoxLayout(this);
setLayout(layout);
cardDisplayLayout = new QVBoxLayout(this);
// Create a QSplitter to hold the ListView and ScrollArea holding CardListdisplayWidgets side by side
auto splitter = new QSplitter(this);
splitter->setOrientation(Qt::Horizontal);
auto listView = new QListView(splitter);
listView->setMinimumWidth(50);
listView->setMaximumWidth(150);
auto listModel = new QStringListModel(this);
QStringList widgetNames;
// Add commander details
auto commanderPicture = new EdhrecCommanderResponseCommanderDetailsDisplayWidget(
this, response.container.getCommanderDetails(), baseUrl);
cardDisplayLayout->addWidget(commanderPicture);
widgetNames.append("Commander Details");
// Add card list widgets
auto edhrec_commander_api_response_card_lists = response.container.getCardlists();
for (const EdhrecApiResponseCardList &card_list : edhrec_commander_api_response_card_lists) {
auto cardListDisplayWidget = new EdhrecApiResponseCardListDisplayWidget(this, card_list);
cardDisplayLayout->addWidget(cardListDisplayWidget);
widgetNames.append(cardListDisplayWidget->getBannerText());
}
// Create a QScrollArea to hold the card display widgets
scrollArea = new QScrollArea(splitter);
scrollArea->setWidgetResizable(true);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// Set the cardDisplayLayout inside the scroll area
auto scrollWidget = new QWidget(scrollArea);
scrollWidget->setLayout(cardDisplayLayout);
connect(splitter, &QSplitter::splitterMoved, this, &EdhrecCommanderApiResponseDisplayWidget::onSplitterChange);
scrollArea->setWidget(scrollWidget);
// Configure the list view
listModel->setStringList(widgetNames);
listView->setModel(listModel);
listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
// Connect the list view to ensure the corresponding widget is visible
connect(listView, &QListView::clicked, this, [this](const QModelIndex &index) {
int widgetIndex = index.row();
qDebug() << "clicked: " << widgetIndex;
auto targetWidget = cardDisplayLayout->itemAt(widgetIndex)->widget();
if (targetWidget) {
qDebug() << "Found targetWidget" << targetWidget;
// Attempt to cast the parent to QScrollArea
auto scrollArea = qobject_cast<QScrollArea *>(this->scrollArea); // Use the scroll area instance
if (scrollArea) {
qDebug() << "ScrollArea" << scrollArea;
scrollArea->ensureWidgetVisible(targetWidget);
}
}
});
// Add splitter to the main layout
splitter->addWidget(listView);
splitter->addWidget(scrollArea);
layout->addWidget(splitter);
}
void EdhrecCommanderApiResponseDisplayWidget::onSplitterChange()
{
scrollArea->widget()->resize(scrollArea->size());
}
void EdhrecCommanderApiResponseDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
qDebug() << event->size();
layout->invalidate();
layout->activate();
layout->update();
if (scrollArea && scrollArea->widget()) {
scrollArea->widget()->resize(event->size());
}
}

View file

@ -0,0 +1,35 @@
/**
* @file edhrec_commander_api_response_display_widget.h
* @ingroup ApiResponseDisplayWidgets
* @brief TODO: Document this.
*/
#ifndef EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H
#define EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H
#include "../../api_response/commander/edhrec_commander_api_response.h"
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>
class EdhrecCommanderApiResponseDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit EdhrecCommanderApiResponseDisplayWidget(QWidget *parent,
EdhrecCommanderApiResponse response,
QString baseUrl);
void resizeEvent(QResizeEvent *event) override;
public slots:
void onSplitterChange();
private:
QHBoxLayout *layout;
QVBoxLayout *cardDisplayLayout;
QScrollArea *scrollArea;
};
#endif // EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H

View file

@ -0,0 +1,174 @@
#include "edhrec_commander_api_response_navigation_widget.h"
#include "../../tab_edhrec_main.h"
EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigationWidget(
QWidget *parent,
const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails,
QString baseUrl)
: QWidget(parent), commanderDetails(_commanderDetails)
{
layout = new QGridLayout(this);
setLayout(layout);
gameChangerLabel = new QLabel(this);
budgetLabel = new QLabel(this);
comboPushButton = new QPushButton(this);
averageDeckPushButton = new QPushButton(this);
layout->addWidget(comboPushButton, 0, 0, 1, 1);
layout->addWidget(averageDeckPushButton, 0, 1, 1, 1);
layout->addWidget(gameChangerLabel, 1, 0, 1, 2);
for (int i = 0; i < gameChangerOptions.length(); i++) {
QString option = gameChangerOptions.at(i);
QString label = option.isEmpty() ? "All" : option.at(0).toUpper() + option.mid(1);
QPushButton *optionButton = new QPushButton(label, this);
gameChangerButtons[option] = optionButton;
layout->addWidget(optionButton, 2, i);
connect(optionButton, &QPushButton::clicked, this, [=, this]() {
selectedGameChanger = option;
updateOptionButtonSelection(gameChangerButtons, option);
actRequestCommanderNavigation();
});
}
layout->addWidget(budgetLabel, 3, 0, 1, 2);
for (int i = 0; i < budgetOptions.length(); i++) {
QString option = budgetOptions.at(i);
QString label = option.isEmpty() ? "Any" : option.at(0).toUpper() + option.mid(1);
QPushButton *btn = new QPushButton(label, this);
budgetButtons[option] = btn;
layout->addWidget(btn, 4, i);
connect(btn, &QPushButton::clicked, this, [=, this]() {
selectedBudget = option;
updateOptionButtonSelection(budgetButtons, option);
actRequestCommanderNavigation();
});
}
updateOptionButtonSelection(gameChangerButtons, "");
updateOptionButtonSelection(budgetButtons, "");
QWidget *currentParent = parentWidget();
TabEdhRecMain *parentTab = nullptr;
while (currentParent) {
if ((parentTab = qobject_cast<TabEdhRecMain *>(currentParent))) {
break;
}
currentParent = currentParent->parentWidget();
}
if (parentTab) {
connect(comboPushButton, &QPushButton::clicked, this,
&EdhrecCommanderApiResponseNavigationWidget::actRequestComboNavigation);
connect(averageDeckPushButton, &QPushButton::clicked, this,
&EdhrecCommanderApiResponseNavigationWidget::actRequestAverageDeckNavigation);
connect(this, &EdhrecCommanderApiResponseNavigationWidget::requestUrl, parentTab,
&TabEdhRecMain::actNavigatePage);
}
retranslateUi();
applyOptionsFromUrl(baseUrl);
}
void EdhrecCommanderApiResponseNavigationWidget::retranslateUi()
{
comboPushButton->setText(tr("Combos"));
averageDeckPushButton->setText(tr("Average Deck"));
gameChangerLabel->setText(tr("Game Changers"));
budgetLabel->setText(tr("Budget"));
}
void EdhrecCommanderApiResponseNavigationWidget::applyOptionsFromUrl(const QString &url)
{
QString cleanedUrl = url;
// Remove base and file extension
if (cleanedUrl.startsWith("https://json.edhrec.com/pages/")) {
cleanedUrl = cleanedUrl.mid(QString("https://json.edhrec.com/pages/").length());
}
if (cleanedUrl.endsWith(".json")) {
cleanedUrl.chop(5);
}
// Expecting something like: "commanders/the-ur-dragon/core/expensive"
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QStringList parts = cleanedUrl.split('/', Qt::SkipEmptyParts);
#else
QStringList parts = cleanedUrl.split('/', QString::SkipEmptyParts);
#endif
if (parts.size() < 2) {
return;
}
QString commanderName = parts[1];
QString gameChangerOpt, budgetOpt;
// Define valid sets
QSet<QString> validGameChangers = {"core", "upgraded", "optimized"};
QSet<QString> validBudgets = {"budget", "expensive"};
// Check remaining parts after commander
for (int i = 2; i < parts.size(); ++i) {
QString part = parts[i].toLower();
if (validGameChangers.contains(part)) {
gameChangerOpt = part;
} else if (validBudgets.contains(part)) {
budgetOpt = part;
}
}
// Validate and apply
if (!gameChangerButtons.contains(gameChangerOpt)) {
gameChangerOpt.clear();
}
if (!budgetButtons.contains(budgetOpt)) {
budgetOpt.clear();
}
selectedGameChanger = gameChangerOpt;
selectedBudget = budgetOpt;
updateOptionButtonSelection(gameChangerButtons, selectedGameChanger);
updateOptionButtonSelection(budgetButtons, selectedBudget);
}
void EdhrecCommanderApiResponseNavigationWidget::updateOptionButtonSelection(QMap<QString, QPushButton *> &buttons,
const QString &selectedKey)
{
for (auto it = buttons.begin(); it != buttons.end(); ++it) {
it.value()->setStyleSheet(it.key() == selectedKey ? "background-color: lightblue; font-weight: bold;" : "");
}
}
QString EdhrecCommanderApiResponseNavigationWidget::addNavigationOptionsToUrl(QString baseUrl)
{
if (!selectedGameChanger.isEmpty()) {
baseUrl += "/" + selectedGameChanger;
}
if (!selectedBudget.isEmpty()) {
baseUrl += "/" + selectedBudget;
}
return baseUrl;
}
void EdhrecCommanderApiResponseNavigationWidget::actRequestCommanderNavigation()
{
emit requestUrl(addNavigationOptionsToUrl("/commanders/" + commanderDetails.getSanitized()));
}
void EdhrecCommanderApiResponseNavigationWidget::actRequestComboNavigation()
{
emit requestUrl("/combos/" + commanderDetails.getSanitized());
}
void EdhrecCommanderApiResponseNavigationWidget::actRequestAverageDeckNavigation()
{
emit requestUrl(addNavigationOptionsToUrl("/average-decks/" + commanderDetails.getSanitized()));
}

View file

@ -0,0 +1,60 @@
/**
* @file edhrec_commander_api_response_navigation_widget.h
* @ingroup ApiResponseDisplayWidgets
* @brief TODO: Document this.
*/
#ifndef EDHREC_COMMANDER_API_RESPONSE_NAVIGATION_WIDGET_H
#define EDHREC_COMMANDER_API_RESPONSE_NAVIGATION_WIDGET_H
#include "edhrec_api_response_commander_details_display_widget.h"
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include <QWidget>
class EdhrecCommanderApiResponseNavigationWidget : public QWidget
{
Q_OBJECT
public:
explicit EdhrecCommanderApiResponseNavigationWidget(
QWidget *parent,
const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails,
QString baseUrl);
void retranslateUi();
void applyOptionsFromUrl(const QString &url);
public slots:
void actRequestCommanderNavigation();
void actRequestComboNavigation();
void actRequestAverageDeckNavigation();
signals:
void requestUrl(QString url);
private:
QGridLayout *layout;
QLabel *gameChangerLabel;
QLabel *budgetLabel;
QStringList gameChangerOptions = {"", "core", "upgraded", "optimized"};
QStringList budgetOptions = {"", "budget", "expensive"};
QString selectedGameChanger;
QString selectedBudget;
QMap<QString, QPushButton *> gameChangerButtons;
QMap<QString, QPushButton *> budgetButtons;
QPushButton *comboPushButton;
QPushButton *averageDeckPushButton;
EdhrecCommanderApiResponseCommanderDetails commanderDetails;
void updateOptionButtonSelection(QMap<QString, QPushButton *> &buttons, const QString &selectedKey);
QString addNavigationOptionsToUrl(QString baseUrl);
QString buildComboUrl() const;
};
#endif // EDHREC_COMMANDER_API_RESPONSE_NAVIGATION_WIDGET_H

View file

@ -0,0 +1,45 @@
#include "edhrec_top_cards_api_response_display_widget.h"
#include "../../api_response/top_cards/edhrec_top_cards_api_response.h"
#include "../cards/edhrec_api_response_card_list_display_widget.h"
EdhrecTopCardsApiResponseDisplayWidget::EdhrecTopCardsApiResponseDisplayWidget(QWidget *parent,
EdhrecTopCardsApiResponse response)
: QWidget(parent)
{
layout = new QHBoxLayout(this);
setLayout(layout);
cardDisplayLayout = new QVBoxLayout(this);
// Add card list widgets
auto edhrec_commander_api_response_card_lists = response.container.getCardlists();
for (const EdhrecApiResponseCardList &card_list : edhrec_commander_api_response_card_lists) {
auto cardListDisplayWidget = new EdhrecApiResponseCardListDisplayWidget(this, card_list);
cardDisplayLayout->addWidget(cardListDisplayWidget);
}
// Create a QScrollArea to hold the card display widgets
scrollArea = new QScrollArea(this);
scrollArea->setWidgetResizable(true);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// Set the cardDisplayLayout inside the scroll area
auto scrollWidget = new QWidget(scrollArea);
scrollWidget->setLayout(cardDisplayLayout);
scrollArea->setWidget(scrollWidget);
layout->addWidget(scrollArea);
}
void EdhrecTopCardsApiResponseDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
layout->invalidate();
layout->activate();
layout->update();
if (scrollArea && scrollArea->widget()) {
scrollArea->widget()->resize(event->size());
}
}

View file

@ -0,0 +1,31 @@
/**
* @file edhrec_top_cards_api_response_display_widget.h
* @ingroup ApiResponseDisplayWidgets
* @brief TODO: Document this.
*/
#ifndef EDHREC_TOP_CARDS_API_RESPONSE_DISPLAY_WIDGET_H
#define EDHREC_TOP_CARDS_API_RESPONSE_DISPLAY_WIDGET_H
#include "../../api_response/top_cards/edhrec_top_cards_api_response.h"
#include <QResizeEvent>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>
class EdhrecTopCardsApiResponseDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit EdhrecTopCardsApiResponseDisplayWidget(QWidget *parent, EdhrecTopCardsApiResponse response);
void resizeEvent(QResizeEvent *event) override;
private:
QHBoxLayout *layout;
QVBoxLayout *cardDisplayLayout;
QScrollArea *scrollArea;
};
#endif // EDHREC_TOP_CARDS_API_RESPONSE_DISPLAY_WIDGET_H

View file

@ -0,0 +1,47 @@
#include "edhrec_top_commanders_api_response_display_widget.h"
#include "../../api_response/top_commanders/edhrec_top_commanders_api_response.h"
#include "../cards/edhrec_api_response_card_list_display_widget.h"
EdhrecTopCommandersApiResponseDisplayWidget::EdhrecTopCommandersApiResponseDisplayWidget(
QWidget *parent,
EdhrecTopCommandersApiResponse response)
: QWidget(parent)
{
layout = new QHBoxLayout(this);
setLayout(layout);
cardDisplayLayout = new QVBoxLayout(this);
// Add card list widgets
auto edhrec_commander_api_response_card_lists = response.container.getCardlists();
for (const EdhrecApiResponseCardList &card_list : edhrec_commander_api_response_card_lists) {
auto cardListDisplayWidget = new EdhrecApiResponseCardListDisplayWidget(this, card_list);
cardDisplayLayout->addWidget(cardListDisplayWidget);
}
// Create a QScrollArea to hold the card display widgets
scrollArea = new QScrollArea(this);
scrollArea->setWidgetResizable(true);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// Set the cardDisplayLayout inside the scroll area
auto scrollWidget = new QWidget(scrollArea);
scrollWidget->setLayout(cardDisplayLayout);
scrollArea->setWidget(scrollWidget);
layout->addWidget(scrollArea);
}
void EdhrecTopCommandersApiResponseDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
qDebug() << event->size();
layout->invalidate();
layout->activate();
layout->update();
if (scrollArea && scrollArea->widget()) {
scrollArea->widget()->resize(event->size());
}
}

View file

@ -0,0 +1,31 @@
/**
* @file edhrec_top_commanders_api_response_display_widget.h
* @ingroup ApiResponseDisplayWidgets
* @brief TODO: Document this.
*/
#ifndef EDHREC_TOP_COMMANDERS_API_RESPONSE_DISPLAY_WIDGET_H
#define EDHREC_TOP_COMMANDERS_API_RESPONSE_DISPLAY_WIDGET_H
#include "../../api_response/top_commanders/edhrec_top_commanders_api_response.h"
#include <QResizeEvent>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>
class EdhrecTopCommandersApiResponseDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit EdhrecTopCommandersApiResponseDisplayWidget(QWidget *parent, EdhrecTopCommandersApiResponse response);
void resizeEvent(QResizeEvent *event) override;
private:
QHBoxLayout *layout;
QVBoxLayout *cardDisplayLayout;
QScrollArea *scrollArea;
};
#endif // EDHREC_TOP_COMMANDERS_API_RESPONSE_DISPLAY_WIDGET_H

View file

@ -0,0 +1,45 @@
#include "edhrec_top_tags_api_response_display_widget.h"
#include "../../api_response/top_tags/edhrec_top_tags_api_response.h"
#include "../cards/edhrec_api_response_card_list_display_widget.h"
EdhrecTopTagsApiResponseDisplayWidget::EdhrecTopTagsApiResponseDisplayWidget(QWidget *parent,
EdhrecTopTagsApiResponse response)
: QWidget(parent)
{
layout = new QHBoxLayout(this);
setLayout(layout);
cardDisplayLayout = new QVBoxLayout(this);
// Add card list widgets
auto edhrec_commander_api_response_card_lists = response.container.getCardlists();
for (const EdhrecApiResponseCardList &card_list : edhrec_commander_api_response_card_lists) {
auto cardListDisplayWidget = new EdhrecApiResponseCardListDisplayWidget(this, card_list);
cardDisplayLayout->addWidget(cardListDisplayWidget);
}
// Create a QScrollArea to hold the card display widgets
scrollArea = new QScrollArea(this);
scrollArea->setWidgetResizable(true);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// Set the cardDisplayLayout inside the scroll area
auto scrollWidget = new QWidget(scrollArea);
scrollWidget->setLayout(cardDisplayLayout);
scrollArea->setWidget(scrollWidget);
layout->addWidget(scrollArea);
}
void EdhrecTopTagsApiResponseDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
layout->invalidate();
layout->activate();
layout->update();
if (scrollArea && scrollArea->widget()) {
scrollArea->widget()->resize(event->size());
}
}

View file

@ -0,0 +1,31 @@
/**
* @file edhrec_top_tags_api_response_display_widget.h
* @ingroup ApiResponseDisplayWidgets
* @brief TODO: Document this.
*/
#ifndef EDHREC_TOP_TAGS_API_RESPONSE_DISPLAY_WIDGET_H
#define EDHREC_TOP_TAGS_API_RESPONSE_DISPLAY_WIDGET_H
#include "../../api_response/top_tags/edhrec_top_tags_api_response.h"
#include <QResizeEvent>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>
class EdhrecTopTagsApiResponseDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit EdhrecTopTagsApiResponseDisplayWidget(QWidget *parent, EdhrecTopTagsApiResponse response);
void resizeEvent(QResizeEvent *event) override;
private:
QHBoxLayout *layout;
QVBoxLayout *cardDisplayLayout;
QScrollArea *scrollArea;
};
#endif // EDHREC_TOP_TAGS_API_RESPONSE_DISPLAY_WIDGET_H

View file

@ -0,0 +1,112 @@
#include "tab_edhrec.h"
#include "api_response/commander/edhrec_commander_api_response.h"
#include "display/commander/edhrec_commander_api_response_display_widget.h"
#include <QDebug>
#include <QHBoxLayout>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QRegularExpression>
#include <QResizeEvent>
TabEdhRec::TabEdhRec(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
{
networkManager = new QNetworkAccessManager(this);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
networkManager->setTransferTimeout(); // Use Qt's default timeout
#endif
networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
connect(networkManager, &QNetworkAccessManager::finished, this, &TabEdhRec::processApiJson);
}
void TabEdhRec::retranslateUi()
{
}
void TabEdhRec::setCard(CardInfoPtr _cardToQuery, bool isCommander)
{
cardToQuery = _cardToQuery;
if (!cardToQuery) {
qDebug() << "Invalid card information provided.";
return;
}
QString cardName = cardToQuery->getName();
QString formattedName = cardName.toLower().replace(" ", "-").remove(QRegularExpression("[^a-z0-9\\-]"));
QString url;
if (isCommander) {
url = QString("https://json.edhrec.com/pages/commanders/%1.json").arg(formattedName);
} else {
url = QString("https://json.edhrec.com/pages/card/%1.json").arg(formattedName);
}
QNetworkRequest request{QUrl(url)};
networkManager->get(request);
}
void TabEdhRec::processApiJson(QNetworkReply *reply)
{
if (reply->error() != QNetworkReply::NoError) {
qDebug() << "Network error occurred:" << reply->errorString();
reply->deleteLater();
return;
}
QByteArray responseData = reply->readAll();
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
if (!jsonDoc.isObject()) {
qDebug() << "Invalid JSON response received.";
reply->deleteLater();
return;
}
QJsonObject jsonObj = jsonDoc.object();
// qDebug() << jsonObj;
EdhrecCommanderApiResponse deckData;
deckData.fromJson(jsonObj);
displayWidget = new EdhrecCommanderApiResponseDisplayWidget(this, deckData, reply->url().toString());
// flowWidget->addWidget(displayWidget);
setCentralWidget(displayWidget);
reply->deleteLater();
update();
}
void TabEdhRec::prettyPrintJson(const QJsonValue &value, int indentLevel)
{
const QString indent(indentLevel * 2, ' '); // Adjust spacing as needed for pretty printing
if (value.isObject()) {
QJsonObject obj = value.toObject();
for (auto it = obj.begin(); it != obj.end(); ++it) {
qDebug().noquote() << indent + it.key() + ":";
prettyPrintJson(it.value(), indentLevel + 1);
}
} else if (value.isArray()) {
QJsonArray array = value.toArray();
for (int i = 0; i < array.size(); ++i) {
qDebug().noquote() << indent + QString("[%1]:").arg(i);
prettyPrintJson(array[i], indentLevel + 1);
}
} else if (value.isString()) {
qDebug().noquote() << indent + "\"" + value.toString() + "\"";
} else if (value.isDouble()) {
qDebug().noquote() << indent + QString::number(value.toDouble());
} else if (value.isBool()) {
qDebug().noquote() << indent + (value.toBool() ? "true" : "false");
} else if (value.isNull()) {
qDebug().noquote() << indent + "null";
}
}

View file

@ -0,0 +1,41 @@
/**
* @file tab_edhrec.h
* @ingroup Tabs
* @brief TODO: Document this.
*/
#ifndef TAB_EDHREC_H
#define TAB_EDHREC_H
#include "../../tab.h"
#include "display/commander/edhrec_commander_api_response_display_widget.h"
#include <QNetworkAccessManager>
#include <libcockatrice/card/card_info.h>
class TabEdhRec : public Tab
{
Q_OBJECT
public:
explicit TabEdhRec(TabSupervisor *_tabSupervisor);
void retranslateUi() override;
QString getTabText() const override
{
auto cardName = cardToQuery.isNull() ? QString() : cardToQuery->getName();
return tr("EDHREC: ") + cardName;
}
QNetworkAccessManager *networkManager;
public slots:
void processApiJson(QNetworkReply *reply);
void prettyPrintJson(const QJsonValue &value, int indentLevel);
void setCard(CardInfoPtr _cardToQuery, bool isCommander = false);
private:
CardInfoPtr cardToQuery;
EdhrecCommanderApiResponseDisplayWidget *displayWidget;
};
#endif // TAB_EDHREC_H

View file

@ -0,0 +1,387 @@
#include "tab_edhrec_main.h"
#include "../../tab_supervisor.h"
#include "api_response/average_deck/edhrec_average_deck_api_response.h"
#include "api_response/commander/edhrec_commander_api_response.h"
#include "api_response/top_cards/edhrec_top_cards_api_response.h"
#include "api_response/top_commanders/edhrec_top_commanders_api_response.h"
#include "api_response/top_tags/edhrec_top_tags_api_response.h"
#include "display/commander/edhrec_commander_api_response_display_widget.h"
#include "display/top_cards/edhrec_top_cards_api_response_display_widget.h"
#include "display/top_commander/edhrec_top_commanders_api_response_display_widget.h"
#include "display/top_tags/edhrec_top_tags_api_response_display_widget.h"
#include <QCompleter>
#include <QDebug>
#include <QHBoxLayout>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QPushButton>
#include <QRegularExpression>
#include <QResizeEvent>
#include <libcockatrice/card/card_database/card_database_manager.h>
#include <libcockatrice/card/card_database/model/card/card_completer_proxy_model.h>
#include <libcockatrice/card/card_database/model/card/card_search_model.h>
static bool canBeCommander(const CardInfoPtr &cardInfo)
{
return ((cardInfo->getCardType().contains("Legendary", Qt::CaseInsensitive) &&
cardInfo->getCardType().contains("Creature", Qt::CaseInsensitive))) ||
cardInfo->getText().contains("can be your commander", Qt::CaseInsensitive);
}
TabEdhRecMain::TabEdhRecMain(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
{
networkManager = new QNetworkAccessManager(this);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
networkManager->setTransferTimeout(); // Use Qt's default timeout
#endif
networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *)));
container = new QWidget(this);
mainLayout = new QVBoxLayout(container);
container->setLayout(mainLayout);
navigationContainer = new QWidget(container);
navigationContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
navigationLayout = new QHBoxLayout(navigationContainer);
navigationLayout->setSpacing(5);
navigationContainer->setLayout(navigationLayout);
cardsPushButton = new QPushButton(navigationContainer);
connect(cardsPushButton, &QPushButton::clicked, this, &TabEdhRecMain::getTopCards);
topCommandersPushButton = new QPushButton(navigationContainer);
connect(topCommandersPushButton, &QPushButton::clicked, this, &TabEdhRecMain::getTopCommanders);
tagsPushButton = new QPushButton(navigationContainer);
connect(tagsPushButton, &QPushButton::clicked, this, &TabEdhRecMain::getTopTags);
searchBar = new QLineEdit(this);
auto cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
auto displayModel = new CardDatabaseDisplayModel(this);
displayModel->setSourceModel(cardDatabaseModel);
CardSearchModel *searchModel = new CardSearchModel(displayModel, this);
CardCompleterProxyModel *proxyModel = new CardCompleterProxyModel(this);
proxyModel->setSourceModel(searchModel);
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
proxyModel->setFilterRole(Qt::DisplayRole);
QCompleter *completer = new QCompleter(proxyModel, this);
completer->setCompletionRole(Qt::DisplayRole);
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setFilterMode(Qt::MatchContains);
completer->setMaxVisibleItems(10);
searchBar->setCompleter(completer);
// Update suggestions dynamically
connect(searchBar, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults);
connect(searchBar, &QLineEdit::textChanged, this, [=](const QString &text) {
// Ensure substring matching
QString pattern = ".*" + QRegularExpression::escape(text) + ".*";
proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
if (!text.isEmpty()) {
completer->complete(); // Force the dropdown to appear
}
});
searchPushButton = new QPushButton(navigationContainer);
connect(searchPushButton, &QPushButton::clicked, this, [=, this]() { doSearch(); });
settingsButton = new SettingsButtonWidget(this);
cardSizeSlider = new CardSizeWidget(this);
settingsButton->addSettingsWidget(cardSizeSlider);
navigationLayout->addWidget(cardsPushButton);
navigationLayout->addWidget(topCommandersPushButton);
navigationLayout->addWidget(tagsPushButton);
navigationLayout->addWidget(searchBar);
navigationLayout->addWidget(searchPushButton);
navigationLayout->addWidget(settingsButton);
currentPageDisplay = new QWidget(container);
currentPageLayout = new QVBoxLayout(currentPageDisplay);
currentPageDisplay->setLayout(currentPageLayout);
mainLayout->addWidget(navigationContainer);
mainLayout->addWidget(currentPageDisplay);
// Ensure navigation stays at the top and currentPageDisplay takes remaining space
mainLayout->setStretch(0, 0); // navigationContainer gets minimum space
mainLayout->setStretch(1, 1); // currentPageDisplay expands as much as possible
setCentralWidget(container);
TabEdhRecMain::retranslateUi();
getTopCards();
}
void TabEdhRecMain::retranslateUi()
{
cardsPushButton->setText(tr("&Cards"));
topCommandersPushButton->setText(tr("Top Commanders"));
tagsPushButton->setText(tr("Tags"));
searchBar->setPlaceholderText(tr("Search for a card ..."));
searchPushButton->setText(tr("Search"));
}
void TabEdhRecMain::doSearch()
{
CardInfoPtr searchedCard = CardDatabaseManager::query()->getCardInfo(searchBar->text());
if (!searchedCard) {
return;
}
setCard(searchedCard, canBeCommander(searchedCard));
}
void TabEdhRecMain::setCard(CardInfoPtr _cardToQuery, bool isCommander)
{
cardToQuery = _cardToQuery;
if (!cardToQuery) {
qDebug() << "Invalid card information provided.";
return;
}
QString cardName = cardToQuery->getName();
QString formattedName = cardName.toLower().replace(" ", "-").remove(QRegularExpression("[^a-z0-9\\-]"));
QString url;
if (isCommander) {
url = QString("https://json.edhrec.com/pages/commanders/%1.json").arg(formattedName);
} else {
url = QString("https://json.edhrec.com/pages/cards/%1.json").arg(formattedName);
}
QNetworkRequest request{QUrl(url)};
networkManager->get(request);
}
void TabEdhRecMain::actNavigatePage(QString url)
{
QNetworkRequest request{QUrl("https://json.edhrec.com/pages" + url + ".json")};
networkManager->get(request);
}
void TabEdhRecMain::getTopCards()
{
QNetworkRequest request{QUrl("https://json.edhrec.com/pages/top/year.json")};
networkManager->get(request);
}
void TabEdhRecMain::getTopCommanders()
{
QNetworkRequest request{QUrl("https://json.edhrec.com/pages/commanders/year.json")};
networkManager->get(request);
}
void TabEdhRecMain::getTopTags()
{
QNetworkRequest request{QUrl("https://json.edhrec.com/pages/tags.json")};
networkManager->get(request);
}
void TabEdhRecMain::processApiJson(QNetworkReply *reply)
{
if (reply->error() != QNetworkReply::NoError) {
qDebug() << "Network error occurred:" << reply->errorString();
reply->deleteLater();
return;
}
QByteArray responseData = reply->readAll();
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
if (!jsonDoc.isObject()) {
qDebug() << "Invalid JSON response received.";
reply->deleteLater();
return;
}
QJsonObject jsonObj = jsonDoc.object();
// Get the actual URL from the reply
QString responseUrl = reply->url().toString();
// Check if the response URL matches a commander request
if (responseUrl.startsWith("https://json.edhrec.com/pages/commanders/year.json")) {
processTopCommandersResponse(jsonObj);
} else if (responseUrl.startsWith("https://json.edhrec.com/pages/commanders/")) {
qInfo() << "Received top kek";
processCommanderResponse(jsonObj, responseUrl);
} else if (responseUrl.startsWith("https://json.edhrec.com/pages/cards/")) {
processCommanderResponse(jsonObj);
} else if (responseUrl.startsWith("https://json.edhrec.com/pages/tags/")) {
processCommanderResponse(jsonObj);
} else if (responseUrl.startsWith("https://json.edhrec.com/pages/tags.json")) {
processTopTagsResponse(jsonObj);
} else if (responseUrl.startsWith("https://json.edhrec.com/pages/top/year.json")) {
processTopCardsResponse(jsonObj);
} else if (responseUrl.startsWith("https://json.edhrec.com/pages/combos/")) {
qInfo() << "Received combos";
processCommanderResponse(jsonObj);
} else if (responseUrl.startsWith("https://json.edhrec.com/pages/average-decks/")) {
processAverageDeckResponse(jsonObj);
} else {
prettyPrintJson(jsonObj, 4);
}
reply->deleteLater();
}
void TabEdhRecMain::processTopCardsResponse(QJsonObject reply)
{
EdhrecTopCardsApiResponse deckData;
deckData.fromJson(reply);
// **Remove previous page display to prevent stacking**
if (currentPageDisplay) {
mainLayout->removeWidget(currentPageDisplay);
delete currentPageDisplay;
currentPageDisplay = nullptr;
}
// **Create new currentPageDisplay**
currentPageDisplay = new QWidget(container);
currentPageLayout = new QVBoxLayout(currentPageDisplay);
currentPageDisplay->setLayout(currentPageLayout);
auto display = new EdhrecTopCardsApiResponseDisplayWidget(currentPageDisplay, deckData);
currentPageLayout->addWidget(display);
mainLayout->addWidget(currentPageDisplay);
// **Ensure layout stays correct**
mainLayout->setStretch(0, 0); // Keep navigationContainer at the top
mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space
}
void TabEdhRecMain::processTopTagsResponse(QJsonObject reply)
{
EdhrecTopTagsApiResponse deckData;
deckData.fromJson(reply);
// **Remove previous page display to prevent stacking**
if (currentPageDisplay) {
mainLayout->removeWidget(currentPageDisplay);
delete currentPageDisplay;
currentPageDisplay = nullptr;
}
// **Create new currentPageDisplay**
currentPageDisplay = new QWidget(container);
currentPageLayout = new QVBoxLayout(currentPageDisplay);
currentPageDisplay->setLayout(currentPageLayout);
auto display = new EdhrecTopTagsApiResponseDisplayWidget(currentPageDisplay, deckData);
currentPageLayout->addWidget(display);
mainLayout->addWidget(currentPageDisplay);
// **Ensure layout stays correct**
mainLayout->setStretch(0, 0); // Keep navigationContainer at the top
mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space
}
void TabEdhRecMain::processTopCommandersResponse(QJsonObject reply)
{
EdhrecTopCommandersApiResponse deckData;
deckData.fromJson(reply);
// **Remove previous page display to prevent stacking**
if (currentPageDisplay) {
mainLayout->removeWidget(currentPageDisplay);
delete currentPageDisplay;
currentPageDisplay = nullptr;
}
// **Create new currentPageDisplay**
currentPageDisplay = new QWidget(container);
currentPageLayout = new QVBoxLayout(currentPageDisplay);
currentPageDisplay->setLayout(currentPageLayout);
auto display = new EdhrecTopCommandersApiResponseDisplayWidget(currentPageDisplay, deckData);
currentPageLayout->addWidget(display);
mainLayout->addWidget(currentPageDisplay);
// **Ensure layout stays correct**
mainLayout->setStretch(0, 0); // Keep navigationContainer at the top
mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space
}
void TabEdhRecMain::processCommanderResponse(QJsonObject reply, QString responseUrl)
{
EdhrecCommanderApiResponse deckData;
deckData.fromJson(reply);
// **Remove previous page display to prevent stacking**
if (currentPageDisplay) {
mainLayout->removeWidget(currentPageDisplay);
delete currentPageDisplay;
currentPageDisplay = nullptr;
}
// **Create new currentPageDisplay**
currentPageDisplay = new QWidget(container);
currentPageLayout = new QVBoxLayout(currentPageDisplay);
currentPageDisplay->setLayout(currentPageLayout);
auto display = new EdhrecCommanderApiResponseDisplayWidget(currentPageDisplay, deckData, responseUrl);
currentPageLayout->addWidget(display);
mainLayout->addWidget(currentPageDisplay);
// **Ensure layout stays correct**
mainLayout->setStretch(0, 0); // Keep navigationContainer at the top
mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space
}
void TabEdhRecMain::processAverageDeckResponse(QJsonObject reply)
{
EdhrecAverageDeckApiResponse deckData;
deckData.fromJson(reply);
tabSupervisor->openDeckInNewTab(deckData.deck.deckLoader);
}
void TabEdhRecMain::prettyPrintJson(const QJsonValue &value, int indentLevel)
{
const QString indent(indentLevel * 2, ' '); // Adjust spacing as needed for pretty printing
if (value.isObject()) {
QJsonObject obj = value.toObject();
for (auto it = obj.begin(); it != obj.end(); ++it) {
qDebug().noquote() << indent + it.key() + ":";
prettyPrintJson(it.value(), indentLevel + 1);
}
} else if (value.isArray()) {
QJsonArray array = value.toArray();
for (int i = 0; i < array.size(); ++i) {
qDebug().noquote() << indent + QString("[%1]:").arg(i);
prettyPrintJson(array[i], indentLevel + 1);
}
} else if (value.isString()) {
qDebug().noquote() << indent + "\"" + value.toString() + "\"";
} else if (value.isDouble()) {
qDebug().noquote() << indent + QString::number(value.toDouble());
} else if (value.isBool()) {
qDebug().noquote() << indent + (value.toBool() ? "true" : "false");
} else if (value.isNull()) {
qDebug().noquote() << indent + "null";
}
}

View file

@ -0,0 +1,75 @@
/**
* @file tab_edhrec_main.h
* @ingroup Tabs
* @brief TODO: Document this.
*/
#ifndef TAB_EDHREC_MAIN_H
#define TAB_EDHREC_MAIN_H
#include "../../interface/widgets/cards/card_size_widget.h"
#include "../../interface/widgets/general/layout_containers/flow_widget.h"
#include "../../interface/widgets/quick_settings/settings_button_widget.h"
#include "../../tab.h"
#include "display/commander/edhrec_commander_api_response_display_widget.h"
#include <QHBoxLayout>
#include <QLineEdit>
#include <QNetworkAccessManager>
#include <QPushButton>
#include <libcockatrice/card/card_database/card_database.h>
class TabEdhRecMain : public Tab
{
Q_OBJECT
public:
explicit TabEdhRecMain(TabSupervisor *_tabSupervisor);
void retranslateUi() override;
void doSearch();
QString getTabText() const override
{
auto cardName = cardToQuery.isNull() ? QString() : cardToQuery->getName();
return tr("EDHRec: ") + cardName;
}
CardSizeWidget *getCardSizeSlider()
{
return cardSizeSlider;
}
QNetworkAccessManager *networkManager;
public slots:
void processApiJson(QNetworkReply *reply);
void processCommanderResponse(QJsonObject reply, QString responseUrl = "");
void processTopCardsResponse(QJsonObject reply);
void processTopTagsResponse(QJsonObject reply);
void processTopCommandersResponse(QJsonObject reply);
void processAverageDeckResponse(QJsonObject reply);
void prettyPrintJson(const QJsonValue &value, int indentLevel);
void setCard(CardInfoPtr _cardToQuery, bool isCommander = false);
void actNavigatePage(QString url);
void getTopCards();
void getTopCommanders();
void getTopTags();
private:
QWidget *container;
QWidget *navigationContainer;
QWidget *currentPageDisplay;
QVBoxLayout *mainLayout;
QHBoxLayout *navigationLayout;
QVBoxLayout *currentPageLayout;
QPushButton *cardsPushButton;
QPushButton *topCommandersPushButton;
QPushButton *tagsPushButton;
QLineEdit *searchBar;
QPushButton *searchPushButton;
SettingsButtonWidget *settingsButton;
CardSizeWidget *cardSizeSlider;
CardInfoPtr cardToQuery;
EdhrecCommanderApiResponseDisplayWidget *displayWidget;
};
#endif // TAB_EDHREC_MAIN_H

View file

@ -0,0 +1,49 @@
#include "tab.h"
#include "../interface/widgets/cards/card_info_display_widget.h"
#include "tab_supervisor.h"
#include <QApplication>
#include <QCloseEvent>
#include <QDebug>
#include <QScreen>
Tab::Tab(TabSupervisor *_tabSupervisor)
: QMainWindow(_tabSupervisor), tabSupervisor(_tabSupervisor), contentsChanged(false), infoPopup(0)
{
setAttribute(Qt::WA_DeleteOnClose);
}
void Tab::showCardInfoPopup(const QPoint &pos, const CardRef &cardRef)
{
if (infoPopup) {
infoPopup->deleteLater();
}
currentCard = cardRef;
infoPopup = new CardInfoDisplayWidget(currentCard, nullptr,
Qt::Widget | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint |
Qt::WindowStaysOnTopHint);
infoPopup->setAttribute(Qt::WA_TransparentForMouseEvents);
auto screenRect = qApp->primaryScreen()->geometry();
infoPopup->move(qMax(screenRect.left(), qMin(pos.x() - infoPopup->width() / 2,
screenRect.left() + screenRect.width() - infoPopup->width())),
qMax(screenRect.top(), qMin(pos.y() - infoPopup->height() / 2,
screenRect.top() + screenRect.height() - infoPopup->height())));
infoPopup->show();
}
void Tab::deleteCardInfoPopup(const QString &cardName)
{
if (infoPopup) {
if (currentCard.name == cardName || cardName == "_") {
infoPopup->deleteLater();
infoPopup = 0;
}
}
}
bool Tab::closeRequest()
{
return close();
}

View file

@ -0,0 +1,75 @@
/**
* @file tab.h
* @ingroup Tabs
* @brief TODO: Document this.
*/
#ifndef TAB_H
#define TAB_H
#include <QMainWindow>
#include <libcockatrice/utility/card_ref.h>
class QMenu;
class TabSupervisor;
class CardInfoDisplayWidget;
class Tab : public QMainWindow
{
Q_OBJECT
signals:
void userEvent(bool globalEvent = true);
void tabTextChanged(Tab *tab, const QString &newTabText);
protected:
TabSupervisor *tabSupervisor;
void addTabMenu(QMenu *menu)
{
tabMenus.append(menu);
}
protected slots:
void showCardInfoPopup(const QPoint &pos, const CardRef &cardRef);
void deleteCardInfoPopup(const QString &cardName);
private:
CardRef currentCard;
bool contentsChanged;
CardInfoDisplayWidget *infoPopup;
QList<QMenu *> tabMenus;
public:
explicit Tab(TabSupervisor *_tabSupervisor);
const QList<QMenu *> &getTabMenus() const
{
return tabMenus;
}
TabSupervisor *getTabSupervisor() const
{
return tabSupervisor;
}
bool getContentsChanged() const
{
return contentsChanged;
}
void setContentsChanged(bool _contentsChanged)
{
contentsChanged = _contentsChanged;
}
virtual QString getTabText() const = 0;
virtual void retranslateUi() = 0;
/**
* Nicely asks to close the tab.
* Override this method to do checks or ask for confirmation before closing the tab.
* If you need to force close the tab, just call close() instead.
*
* @return True if the tab is successfully closed.
*/
virtual bool closeRequest();
virtual void tabActivated()
{
}
};
#endif

View file

@ -0,0 +1,239 @@
#include "tab_account.h"
#include "../client/sound_engine.h"
#include "../interface/widgets/server/user/user_info_box.h"
#include "../interface/widgets/server/user/user_list_manager.h"
#include "../interface/widgets/server/user/user_list_widget.h"
#include "../interface/widgets/utility/custom_line_edit.h"
#include "tab_supervisor.h"
#include <QPushButton>
#include <QVBoxLayout>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pb/event_add_to_list.pb.h>
#include <libcockatrice/protocol/pb/event_remove_from_list.pb.h>
#include <libcockatrice/protocol/pb/event_user_joined.pb.h>
#include <libcockatrice/protocol/pb/event_user_left.pb.h>
#include <libcockatrice/protocol/pb/response_list_users.pb.h>
#include <libcockatrice/protocol/pb/session_commands.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/trice_limits.h>
TabAccount::TabAccount(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User &userInfo)
: Tab(_tabSupervisor), client(_client)
{
allUsersList = new UserListWidget(_tabSupervisor, client, UserListWidget::AllUsersList);
buddyList = new UserListWidget(_tabSupervisor, client, UserListWidget::BuddyList);
ignoreList = new UserListWidget(_tabSupervisor, client, UserListWidget::IgnoreList);
userInfoBox = new UserInfoBox(client, true);
userInfoBox->updateInfo(userInfo);
connect(allUsersList, &UserListWidget::openMessageDialog, this, &TabAccount::openMessageDialog);
connect(buddyList, &UserListWidget::openMessageDialog, this, &TabAccount::openMessageDialog);
connect(ignoreList, &UserListWidget::openMessageDialog, this, &TabAccount::openMessageDialog);
connect(client, &AbstractClient::userJoinedEventReceived, this, &TabAccount::processUserJoinedEvent);
connect(client, &AbstractClient::userLeftEventReceived, this, &TabAccount::processUserLeftEvent);
connect(client, &AbstractClient::buddyListReceived, this, &TabAccount::buddyListReceived);
connect(client, &AbstractClient::ignoreListReceived, this, &TabAccount::ignoreListReceived);
connect(client, &AbstractClient::addToListEventReceived, this, &TabAccount::processAddToListEvent);
connect(client, &AbstractClient::removeFromListEventReceived, this, &TabAccount::processRemoveFromListEvent);
// Attempt to populate the tab with the cache
buddyListReceived(tabSupervisor->getUserListManager()->getBuddyList().values());
ignoreListReceived(tabSupervisor->getUserListManager()->getIgnoreList().values());
PendingCommand *pend = AbstractClient::prepareSessionCommand(Command_ListUsers());
connect(pend, &PendingCommand::finished, this, &TabAccount::processListUsersResponse);
client->sendCommand(pend);
auto *vbox = new QVBoxLayout;
vbox->addWidget(userInfoBox);
vbox->addWidget(allUsersList);
auto *addToBuddyList = new QHBoxLayout;
addBuddyEdit = new LineEditUnfocusable;
addBuddyEdit->setMaxLength(MAX_NAME_LENGTH);
addBuddyEdit->setPlaceholderText(tr("Add to Buddy List"));
connect(addBuddyEdit, &LineEditUnfocusable::returnPressed, this, &TabAccount::addToBuddyList);
auto *addBuddyButton = new QPushButton("Add");
connect(addBuddyButton, &QPushButton::clicked, this, &TabAccount::addToBuddyList);
addToBuddyList->addWidget(addBuddyEdit);
addToBuddyList->addWidget(addBuddyButton);
auto *addToIgnoreList = new QHBoxLayout;
addIgnoreEdit = new LineEditUnfocusable;
addIgnoreEdit->setMaxLength(MAX_NAME_LENGTH);
addIgnoreEdit->setPlaceholderText(tr("Add to Ignore List"));
connect(addIgnoreEdit, &LineEditUnfocusable::returnPressed, this, &TabAccount::addToIgnoreList);
auto *addIgnoreButton = new QPushButton("Add");
connect(addIgnoreButton, &QPushButton::clicked, this, &TabAccount::addToIgnoreList);
addToIgnoreList->addWidget(addIgnoreEdit);
addToIgnoreList->addWidget(addIgnoreButton);
auto *buddyPanel = new QVBoxLayout;
buddyPanel->addWidget(buddyList);
buddyPanel->addLayout(addToBuddyList);
auto *ignorePanel = new QVBoxLayout;
ignorePanel->addWidget(ignoreList);
ignorePanel->addLayout(addToIgnoreList);
auto *mainLayout = new QHBoxLayout;
mainLayout->addLayout(buddyPanel);
mainLayout->addLayout(ignorePanel);
mainLayout->addLayout(vbox);
retranslateUi();
auto *mainWidget = new QWidget(this);
mainWidget->setLayout(mainLayout);
setCentralWidget(mainWidget);
}
void TabAccount::addToBuddyList()
{
const QString &userName = addBuddyEdit->text();
if (userName.isEmpty()) {
return;
}
const std::string listName = "buddy";
addToList(listName, userName);
addBuddyEdit->clear();
}
void TabAccount::addToIgnoreList()
{
const QString &userName = addIgnoreEdit->text();
if (userName.isEmpty()) {
return;
}
const std::string listName = "ignore";
addToList(listName, userName);
addIgnoreEdit->clear();
}
void TabAccount::addToList(const std::string &listName, const QString &userName)
{
Command_AddToList cmd;
cmd.set_list(listName);
cmd.set_user_name(userName.toStdString());
client->sendCommand(AbstractClient::prepareSessionCommand(cmd));
}
void TabAccount::retranslateUi()
{
allUsersList->retranslateUi();
buddyList->retranslateUi();
ignoreList->retranslateUi();
userInfoBox->retranslateUi();
}
void TabAccount::processListUsersResponse(const Response &response)
{
const Response_ListUsers &resp = response.GetExtension(Response_ListUsers::ext);
for (int i = 0; i < resp.user_list_size(); ++i) {
const ServerInfo_User &info = resp.user_list(i);
const QString &userName = QString::fromStdString(info.name());
allUsersList->processUserInfo(info, true);
ignoreList->setUserOnline(userName, true);
buddyList->setUserOnline(userName, true);
}
allUsersList->sortItems();
ignoreList->sortItems();
buddyList->sortItems();
}
void TabAccount::processUserJoinedEvent(const Event_UserJoined &event)
{
const ServerInfo_User &info = event.user_info();
const QString &userName = QString::fromStdString(info.name());
allUsersList->processUserInfo(info, true);
ignoreList->setUserOnline(userName, true);
buddyList->setUserOnline(userName, true);
allUsersList->sortItems();
ignoreList->sortItems();
buddyList->sortItems();
if (buddyList->getUsers().keys().contains(userName)) {
soundEngine->playSound("buddy_join");
}
emit userJoined(info);
}
void TabAccount::processUserLeftEvent(const Event_UserLeft &event)
{
const QString &userName = QString::fromStdString(event.name());
if (buddyList->getUsers().keys().contains(userName)) {
soundEngine->playSound("buddy_leave");
}
if (allUsersList->deleteUser(userName)) {
ignoreList->setUserOnline(userName, false);
buddyList->setUserOnline(userName, false);
ignoreList->sortItems();
buddyList->sortItems();
emit userLeft(userName);
}
}
void TabAccount::buddyListReceived(const QList<ServerInfo_User> &_buddyList)
{
for (const auto &user : _buddyList) {
buddyList->processUserInfo(user, false);
}
buddyList->sortItems();
}
void TabAccount::ignoreListReceived(const QList<ServerInfo_User> &_ignoreList)
{
for (const auto &user : _ignoreList) {
ignoreList->processUserInfo(user, false);
}
ignoreList->sortItems();
}
void TabAccount::processAddToListEvent(const Event_AddToList &event)
{
const ServerInfo_User &info = event.user_info();
const bool online = allUsersList->getUsers().contains(QString::fromStdString(info.name()));
const QString &list = QString::fromStdString(event.list_name());
UserListWidget *userList;
if (list == "buddy") {
userList = buddyList;
} else if (list == "ignore") {
userList = ignoreList;
} else {
return;
}
userList->processUserInfo(info, online);
userList->sortItems();
}
void TabAccount::processRemoveFromListEvent(const Event_RemoveFromList &event)
{
const auto &list = QString::fromStdString(event.list_name());
const auto &user = QString::fromStdString(event.user_name());
UserListWidget *userList;
if (list == "buddy") {
userList = buddyList;
} else if (list == "ignore") {
userList = ignoreList;
} else {
return;
}
userList->deleteUser(user);
}

View file

@ -0,0 +1,64 @@
/**
* @file tab_account.h
* @ingroup AccountTabs
* @brief TODO: Document this.
*/
#ifndef TAB_ACCOUNT_H
#define TAB_ACCOUNT_H
#include "tab.h"
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
class AbstractClient;
class Event_AddToList;
class Event_ListRooms;
class Event_RemoveFromList;
class Event_UserJoined;
class Event_UserLeft;
class LineEditUnfocusable;
class Response;
class ServerInfo_User;
class UserInfoBox;
class UserListWidget;
class TabAccount : public Tab
{
Q_OBJECT
signals:
void openMessageDialog(const QString &userName, bool focus);
void userLeft(const QString &userName);
void userJoined(const ServerInfo_User &userInfo);
private slots:
void processListUsersResponse(const Response &response);
void processUserJoinedEvent(const Event_UserJoined &event);
void processUserLeftEvent(const Event_UserLeft &event);
void buddyListReceived(const QList<ServerInfo_User> &_buddyList);
void ignoreListReceived(const QList<ServerInfo_User> &_ignoreList);
void processAddToListEvent(const Event_AddToList &event);
void processRemoveFromListEvent(const Event_RemoveFromList &event);
void addToIgnoreList();
void addToBuddyList();
private:
AbstractClient *client;
UserListWidget *allUsersList;
UserListWidget *buddyList;
UserListWidget *ignoreList;
UserInfoBox *userInfoBox;
LineEditUnfocusable *addBuddyEdit;
LineEditUnfocusable *addIgnoreEdit;
void addToList(const std::string &listName, const QString &userName);
public:
explicit TabAccount(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User &userInfo);
void retranslateUi() override;
[[nodiscard]] QString getTabText() const override
{
return tr("Account");
}
};
#endif

View file

@ -0,0 +1,263 @@
#include "tab_admin.h"
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QSpinBox>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pb/admin_commands.pb.h>
#include <libcockatrice/protocol/pb/event_replay_added.pb.h>
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/trice_limits.h>
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)
: Tab(_tabSupervisor), 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);
auto *adminVBox = new QVBoxLayout;
adminVBox->addWidget(updateServerMessageButton);
adminVBox->addWidget(shutdownServerButton);
adminVBox->addWidget(reloadConfigButton);
adminGroupBox = new QGroupBox;
adminGroupBox->setLayout(adminVBox);
adminGroupBox->setEnabled(false);
auto *moderatorVBox = new QVBoxLayout;
moderatorVBox->addLayout(grandReplayAccessLayout);
moderatorVBox->addLayout(activateUserLayout);
moderatorGroupBox = new QGroupBox;
moderatorGroupBox->setLayout(moderatorVBox);
moderatorGroupBox->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(moderatorGroupBox);
mainLayout->addStretch();
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"));
moderatorGroupBox->setTitle(tr("Server moderator 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, &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)
{
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)
{
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);
}
moderatorGroupBox->setEnabled(true);
lockButton->setEnabled(true);
unlockButton->setEnabled(false);
locked = false;
emit adminLockChanged(false);
}
void TabAdmin::actLock()
{
if (fullAdmin) {
adminGroupBox->setEnabled(false);
}
moderatorGroupBox->setEnabled(false);
lockButton->setEnabled(false);
unlockButton->setEnabled(true);
locked = true;
emit adminLockChanged(true);
}

View file

@ -0,0 +1,75 @@
/**
* @file tab_admin.h
* @ingroup ServerTabs
* @brief TODO: Document this.
*/
#ifndef TAB_ADMIN_H
#define TAB_ADMIN_H
#include "tab.h"
#include <QDialog>
#include <libcockatrice/protocol/pb/commands.pb.h>
#include <libcockatrice/protocol/pb/response.pb.h>
class AbstractClient;
class QGroupBox;
class QPushButton;
class QSpinBox;
class QLineEdit;
class ShutdownDialog : public QDialog
{
Q_OBJECT
private:
QLineEdit *reasonEdit;
QSpinBox *minutesEdit;
public:
explicit ShutdownDialog(QWidget *parent = nullptr);
QString getReason() const;
int getMinutes() const;
};
class TabAdmin : public Tab
{
Q_OBJECT
private:
bool locked;
AbstractClient *client;
bool fullAdmin;
QPushButton *updateServerMessageButton, *shutdownServerButton, *reloadConfigButton, *grantReplayAccessButton,
*activateUserButton;
QGroupBox *adminGroupBox, *moderatorGroupBox;
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 &response);
void activateUserProcessResponse(const Response &response);
void actUnlock();
void actLock();
public:
TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin);
void retranslateUi() override;
QString getTabText() const override
{
return tr("Administration");
}
bool getLocked() const
{
return locked;
}
};
#endif

View file

@ -0,0 +1,374 @@
#include "tab_deck_editor.h"
#include "../client/network/interfaces/tapped_out_interface.h"
#include "../dialogs/dlg_load_deck.h"
#include "../dialogs/dlg_load_deck_from_clipboard.h"
#include "../filters/filter_builder.h"
#include "../filters/filter_tree_model.h"
#include "../interface/pixel_map_generator.h"
#include "../interface/widgets/cards/card_info_frame_widget.h"
#include "../interface/widgets/deck_editor/deck_editor_filter_dock_widget.h"
#include "../interface/widgets/menus/deck_editor_menu.h"
#include "tab_supervisor.h"
#include <QAction>
#include <QApplication>
#include <QCloseEvent>
#include <QComboBox>
#include <QDebug>
#include <QDir>
#include <QDockWidget>
#include <QFileDialog>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QMenuBar>
#include <QMessageBox>
#include <QPrintPreviewDialog>
#include <QProcessEnvironment>
#include <QSplitter>
#include <QTextEdit>
#include <QTimer>
#include <QToolButton>
#include <QTreeView>
#include <QVBoxLayout>
#include <libcockatrice/card/card_database/card_database_manager.h>
#include <libcockatrice/card/card_database/model/card_database_model.h>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/settings/cache_settings.h>
#include <libcockatrice/utility/trice_limits.h>
TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor) : AbstractTabDeckEditor(_tabSupervisor)
{
setObjectName("TabDeckEditor");
TabDeckEditor::createMenus();
installEventFilter(this);
TabDeckEditor::retranslateUi();
TabDeckEditor::refreshShortcuts();
TabDeckEditor::loadLayout();
}
void TabDeckEditor::createMenus()
{
deckMenu = new DeckEditorMenu(this);
addTabMenu(deckMenu);
viewMenu = new QMenu(this);
cardInfoDockMenu = viewMenu->addMenu(QString());
deckDockMenu = viewMenu->addMenu(QString());
filterDockMenu = viewMenu->addMenu(QString());
printingSelectorDockMenu = viewMenu->addMenu(QString());
aCardInfoDockVisible = cardInfoDockMenu->addAction(QString());
aCardInfoDockVisible->setCheckable(true);
connect(aCardInfoDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered);
aCardInfoDockFloating = cardInfoDockMenu->addAction(QString());
aCardInfoDockFloating->setCheckable(true);
connect(aCardInfoDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered);
aDeckDockVisible = deckDockMenu->addAction(QString());
aDeckDockVisible->setCheckable(true);
connect(aDeckDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered);
aDeckDockFloating = deckDockMenu->addAction(QString());
aDeckDockFloating->setCheckable(true);
connect(aDeckDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered);
aFilterDockVisible = filterDockMenu->addAction(QString());
aFilterDockVisible->setCheckable(true);
connect(aFilterDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered);
aFilterDockFloating = filterDockMenu->addAction(QString());
aFilterDockFloating->setCheckable(true);
connect(aFilterDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered);
aPrintingSelectorDockVisible = printingSelectorDockMenu->addAction(QString());
aPrintingSelectorDockVisible->setCheckable(true);
connect(aPrintingSelectorDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered);
aPrintingSelectorDockFloating = printingSelectorDockMenu->addAction(QString());
aPrintingSelectorDockFloating->setCheckable(true);
connect(aPrintingSelectorDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered);
if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) {
printingSelectorDockMenu->setEnabled(false);
}
connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this,
[this](bool enabled) { printingSelectorDockMenu->setEnabled(!enabled); });
viewMenu->addSeparator();
aResetLayout = viewMenu->addAction(QString());
connect(aResetLayout, &QAction::triggered, this, &TabDeckEditor::restartLayout);
viewMenu->addAction(aResetLayout);
deckMenu->setSaveStatus(false);
addTabMenu(viewMenu);
}
QString TabDeckEditor::getTabText() const
{
QString result = tr("Deck: %1").arg(deckDockWidget->getSimpleDeckName());
if (modified)
result.prepend("* ");
return result;
}
void TabDeckEditor::retranslateUi()
{
deckMenu->retranslateUi();
cardInfoDockWidget->retranslateUi();
deckDockWidget->retranslateUi();
filterDockWidget->retranslateUi();
printingSelectorDockWidget->retranslateUi();
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"));
aDeckDockVisible->setText(tr("Visible"));
aDeckDockFloating->setText(tr("Floating"));
aFilterDockVisible->setText(tr("Visible"));
aFilterDockFloating->setText(tr("Floating"));
aPrintingSelectorDockVisible->setText(tr("Visible"));
aPrintingSelectorDockFloating->setText(tr("Floating"));
aResetLayout->setText(tr("Reset layout"));
}
void TabDeckEditor::refreshShortcuts()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
aResetLayout->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aResetLayout"));
}
void TabDeckEditor::showPrintingSelector()
{
printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr(),
DECK_ZONE_MAIN);
printingSelectorDockWidget->printingSelector->updateDisplay();
aPrintingSelectorDockVisible->setChecked(true);
printingSelectorDockWidget->setVisible(true);
}
void TabDeckEditor::loadLayout()
{
LayoutsSettings &layouts = SettingsCache::instance().layouts();
setCentralWidget(databaseDisplayDockWidget);
auto &layoutState = layouts.getDeckEditorLayoutState();
if (layoutState.isNull()) {
restartLayout();
} else {
restoreState(layoutState);
restoreGeometry(layouts.getDeckEditorGeometry());
}
if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) {
if (!printingSelectorDockWidget->isHidden()) {
printingSelectorDockWidget->setHidden(true);
aPrintingSelectorDockVisible->setChecked(false);
}
}
aCardInfoDockVisible->setChecked(!cardInfoDockWidget->isHidden());
aFilterDockVisible->setChecked(!filterDockWidget->isHidden());
aDeckDockVisible->setChecked(!deckDockWidget->isHidden());
aPrintingSelectorDockVisible->setChecked(!printingSelectorDockWidget->isHidden());
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
aCardInfoDockFloating->setChecked(cardInfoDockWidget->isFloating());
aFilterDockFloating->setChecked(filterDockWidget->isFloating());
aDeckDockFloating->setChecked(deckDockWidget->isFloating());
aPrintingSelectorDockFloating->setChecked(printingSelectorDockWidget->isFloating());
cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize());
cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize());
filterDockWidget->setMinimumSize(layouts.getDeckEditorFilterSize());
filterDockWidget->setMaximumSize(layouts.getDeckEditorFilterSize());
deckDockWidget->setMinimumSize(layouts.getDeckEditorDeckSize());
deckDockWidget->setMaximumSize(layouts.getDeckEditorDeckSize());
printingSelectorDockWidget->setMinimumSize(layouts.getDeckEditorPrintingSelectorSize());
printingSelectorDockWidget->setMaximumSize(layouts.getDeckEditorPrintingSelectorSize());
QTimer::singleShot(100, this, &TabDeckEditor::freeDocksSize);
}
void TabDeckEditor::restartLayout()
{
aCardInfoDockVisible->setChecked(true);
aDeckDockVisible->setChecked(true);
aFilterDockVisible->setChecked(true);
aPrintingSelectorDockVisible->setChecked(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference());
aCardInfoDockFloating->setChecked(false);
aDeckDockFloating->setChecked(false);
aFilterDockFloating->setChecked(false);
aPrintingSelectorDockFloating->setChecked(false);
setCentralWidget(databaseDisplayDockWidget);
addDockWidget(Qt::RightDockWidgetArea, deckDockWidget);
addDockWidget(Qt::RightDockWidgetArea, cardInfoDockWidget);
addDockWidget(Qt::RightDockWidgetArea, filterDockWidget);
addDockWidget(Qt::RightDockWidgetArea, printingSelectorDockWidget);
deckDockWidget->setFloating(false);
cardInfoDockWidget->setFloating(false);
filterDockWidget->setFloating(false);
printingSelectorDockWidget->setFloating(false);
deckDockWidget->setVisible(true);
cardInfoDockWidget->setVisible(true);
filterDockWidget->setVisible(true);
printingSelectorDockWidget->setVisible(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference());
splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Horizontal);
splitDockWidget(printingSelectorDockWidget, deckDockWidget, Qt::Horizontal);
splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Horizontal);
splitDockWidget(cardInfoDockWidget, filterDockWidget, Qt::Vertical);
QTimer::singleShot(100, this, &TabDeckEditor::freeDocksSize);
}
void TabDeckEditor::freeDocksSize()
{
deckDockWidget->setMinimumSize(100, 100);
deckDockWidget->setMaximumSize(5000, 5000);
cardInfoDockWidget->setMinimumSize(100, 100);
cardInfoDockWidget->setMaximumSize(5000, 5000);
filterDockWidget->setMinimumSize(100, 100);
filterDockWidget->setMaximumSize(5000, 5000);
printingSelectorDockWidget->setMinimumSize(100, 100);
printingSelectorDockWidget->setMaximumSize(5000, 5000);
}
void TabDeckEditor::dockVisibleTriggered()
{
QObject *o = sender();
if (o == aCardInfoDockVisible) {
cardInfoDockWidget->setHidden(!aCardInfoDockVisible->isChecked());
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
return;
}
if (o == aDeckDockVisible) {
deckDockWidget->setHidden(!aDeckDockVisible->isChecked());
aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
return;
}
if (o == aFilterDockVisible) {
filterDockWidget->setHidden(!aFilterDockVisible->isChecked());
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
return;
}
if (o == aPrintingSelectorDockVisible) {
printingSelectorDockWidget->setHidden(!aPrintingSelectorDockVisible->isChecked());
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
return;
}
}
void TabDeckEditor::dockFloatingTriggered()
{
QObject *o = sender();
if (o == aCardInfoDockFloating) {
cardInfoDockWidget->setFloating(aCardInfoDockFloating->isChecked());
return;
}
if (o == aDeckDockFloating) {
deckDockWidget->setFloating(aDeckDockFloating->isChecked());
return;
}
if (o == aFilterDockFloating) {
filterDockWidget->setFloating(aFilterDockFloating->isChecked());
return;
}
if (o == aPrintingSelectorDockFloating) {
printingSelectorDockWidget->setFloating(aPrintingSelectorDockFloating->isChecked());
return;
}
}
void TabDeckEditor::dockTopLevelChanged(bool topLevel)
{
QObject *o = sender();
if (o == cardInfoDockWidget) {
aCardInfoDockFloating->setChecked(topLevel);
return;
}
if (o == deckDockWidget) {
aDeckDockFloating->setChecked(topLevel);
return;
}
if (o == filterDockWidget) {
aFilterDockFloating->setChecked(topLevel);
return;
}
if (o == printingSelectorDockWidget) {
aPrintingSelectorDockFloating->setChecked(topLevel);
return;
}
}
// Method uses to sync docks state with menu items state
bool TabDeckEditor::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Close) {
if (o == cardInfoDockWidget) {
aCardInfoDockVisible->setChecked(false);
aCardInfoDockFloating->setEnabled(false);
} else if (o == deckDockWidget) {
aDeckDockVisible->setChecked(false);
aDeckDockFloating->setEnabled(false);
} else if (o == filterDockWidget) {
aFilterDockVisible->setChecked(false);
aFilterDockFloating->setEnabled(false);
} else if (o == printingSelectorDockWidget) {
aPrintingSelectorDockVisible->setChecked(false);
aPrintingSelectorDockFloating->setEnabled(false);
}
}
if (o == this && e->type() == QEvent::Hide) {
LayoutsSettings &layouts = SettingsCache::instance().layouts();
layouts.setDeckEditorLayoutState(saveState());
layouts.setDeckEditorGeometry(saveGeometry());
layouts.setDeckEditorCardSize(cardInfoDockWidget->size());
layouts.setDeckEditorFilterSize(filterDockWidget->size());
layouts.setDeckEditorDeckSize(deckDockWidget->size());
layouts.setDeckEditorPrintingSelectorSize(printingSelectorDockWidget->size());
}
return false;
}

View file

@ -0,0 +1,42 @@
#ifndef WINDOW_DECKEDITOR_H
#define WINDOW_DECKEDITOR_H
#include "../interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h"
#include "abstract_tab_deck_editor.h"
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/utility/key_signals.h>
class CardDatabaseModel;
class CardDatabaseDisplayModel;
class DeckListModel;
class QLabel;
class DeckLoader;
class TabDeckEditor : public AbstractTabDeckEditor
{
Q_OBJECT
protected slots:
void loadLayout() override;
void restartLayout() override;
void freeDocksSize() override;
void refreshShortcuts() override;
bool eventFilter(QObject *o, QEvent *e) override;
void dockVisibleTriggered() override;
void dockFloatingTriggered() override;
void dockTopLevelChanged(bool topLevel) override;
public:
explicit TabDeckEditor(TabSupervisor *_tabSupervisor);
void retranslateUi() override;
QString getTabText() const override;
void createMenus() override;
public slots:
void showPrintingSelector() override;
};
#endif

View file

@ -0,0 +1,604 @@
#include "tab_deck_storage.h"
#include "../interface/widgets/server/remote/remote_decklist_tree_widget.h"
#include "../interface/widgets/utility/get_text_with_max.h"
#include <QAction>
#include <QApplication>
#include <QDebug>
#include <QDesktopServices>
#include <QFileSystemModel>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QInputDialog>
#include <QMessageBox>
#include <QToolBar>
#include <QTreeView>
#include <QUrl>
#include <QVBoxLayout>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/deck_loader.h>
#include <libcockatrice/protocol/pb/command_deck_del.pb.h>
#include <libcockatrice/protocol/pb/command_deck_del_dir.pb.h>
#include <libcockatrice/protocol/pb/command_deck_download.pb.h>
#include <libcockatrice/protocol/pb/command_deck_new_dir.pb.h>
#include <libcockatrice/protocol/pb/command_deck_upload.pb.h>
#include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/protocol/pb/response_deck_download.pb.h>
#include <libcockatrice/protocol/pb/response_deck_upload.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/settings/cache_settings.h>
TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor,
AbstractClient *_client,
const ServerInfo_User *currentUserInfo)
: Tab(_tabSupervisor), client(_client)
{
localDirModel = new QFileSystemModel(this);
localDirModel->setRootPath(SettingsCache::instance().getDeckPath());
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);
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));
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 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, &QAction::triggered, this, &TabDeckStorage::actOpenLocalDeck);
aRenameLocal = new QAction(this);
aRenameLocal->setIcon(QPixmap("theme:icons/rename"));
connect(aRenameLocal, &QAction::triggered, this, &TabDeckStorage::actRenameLocal);
aUpload = new QAction(this);
aUpload->setIcon(QPixmap("theme:icons/arrow_right_green"));
connect(aUpload, &QAction::triggered, this, &TabDeckStorage::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, &QAction::triggered, this, &TabDeckStorage::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, &QAction::triggered, this, &TabDeckStorage::actOpenRemoteDeck);
aDownload = new QAction(this);
aDownload->setIcon(QPixmap("theme:icons/arrow_left_green"));
connect(aDownload, &QAction::triggered, this, &TabDeckStorage::actDownload);
aNewFolder = new QAction(this);
aNewFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder));
connect(aNewFolder, &QAction::triggered, this, &TabDeckStorage::actNewFolder);
aDeleteRemoteDeck = new QAction(this);
aDeleteRemoteDeck->setIcon(QPixmap("theme:icons/remove_row"));
connect(aDeleteRemoteDeck, &QAction::triggered, this, &TabDeckStorage::actDeleteRemoteDeck);
// Add actions to toolbars
leftToolBar->addAction(aOpenLocalDeck);
leftToolBar->addAction(aRenameLocal);
leftToolBar->addAction(aUpload);
leftToolBar->addAction(aNewLocalFolder);
leftToolBar->addAction(aDeleteLocalDeck);
leftRightmostToolBar->addAction(aOpenDecksFolder);
rightToolBar->addAction(aOpenRemoteDeck);
rightToolBar->addAction(aDownload);
rightToolBar->addAction(aNewFolder);
rightToolBar->addAction(aDeleteRemoteDeck);
retranslateUi();
QWidget *mainWidget = new QWidget(this);
mainWidget->setLayout(hbox);
setCentralWidget(mainWidget);
connect(client, &AbstractClient::userInfoChanged, this, &TabDeckStorage::handleConnected);
connect(client, &AbstractClient::statusChanged, this, &TabDeckStorage::handleConnectionChanged);
setRemoteEnabled(currentUserInfo && currentUserInfo->user_level() & ServerInfo_User::IsRegistered);
}
void TabDeckStorage::retranslateUi()
{
leftGroupBox->setTitle(tr("Local file system"));
rightGroupBox->setTitle(tr("Server deck storage"));
aOpenLocalDeck->setText(tr("Open in deck editor"));
aRenameLocal->setText(tr("Rename deck or folder"));
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
{
RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem();
if (curRight == nullptr)
return {};
auto *dir = dynamic_cast<RemoteDeckList_TreeModel::DirectoryNode *>(curRight);
if (dir == nullptr) {
dir = dynamic_cast<RemoteDeckList_TreeModel::DirectoryNode *>(curRight->getParent());
if (dir != nullptr) {
return dir->getPath();
} else {
return {};
}
} else {
return dir->getPath();
}
}
void TabDeckStorage::handleConnected(const ServerInfo_User &userInfo)
{
setRemoteEnabled(userInfo.user_level() & ServerInfo_User::IsRegistered);
}
/**
* This is only responsible for handling the disconnect. The connect is already handled elsewhere
*/
void TabDeckStorage::handleConnectionChanged(ClientStatus status)
{
if (status == StatusDisconnected) {
setRemoteEnabled(false);
}
}
void TabDeckStorage::setRemoteEnabled(bool enabled)
{
aUpload->setEnabled(enabled);
aOpenRemoteDeck->setEnabled(enabled);
aDownload->setEnabled(enabled);
aNewFolder->setEnabled(enabled);
aDeleteRemoteDeck->setEnabled(enabled);
if (enabled) {
serverDirView->refreshTree();
} else {
serverDirView->clearTree();
}
}
void TabDeckStorage::actLocalDoubleClick(const QModelIndex &curLeft)
{
if (!localDirModel->isDir(curLeft)) {
actOpenLocalDeck();
}
}
void TabDeckStorage::actOpenLocalDeck()
{
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, true))
continue;
emit openDeckEditor(&deckLoader);
}
}
void TabDeckStorage::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 TabDeckStorage::actUpload()
{
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;
}
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);
DeckLoader deck;
if (!deck.loadFromFile(filePath, DeckLoader::CockatriceFormat)) {
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
return;
}
if (deck.getName().isEmpty()) {
bool ok;
QString deckName =
getTextWithMax(this, tr("Enter deck name"), tr("This decklist does not have a name.\nPlease enter a name:"),
QLineEdit::Normal, deckFileInfo.completeBaseName(), &ok);
if (!ok)
return;
if (deckName.isEmpty())
deckName = tr("Unnamed deck");
deck.setName(deckName);
} else {
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());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabDeckStorage::uploadFinished);
client->sendCommand(pend);
}
void TabDeckStorage::uploadFinished(const Response &r, const CommandContainer &commandContainer)
{
if (r.response_code() != Response::RespOk) {
qCritical() << "failed to upload deck:" << r.response_code();
QMessageBox::critical(this, tr("Error"), tr("Failed to upload deck to server"));
return;
}
const Response_DeckUpload &resp = r.GetExtension(Response_DeckUpload::ext);
const Command_DeckUpload &cmd = commandContainer.session_command(0).GetExtension(Command_DeckUpload::ext);
serverDirView->addFileToTree(resp.new_file(), serverDirView->getNodeByPath(QString::fromStdString(cmd.path())));
}
void TabDeckStorage::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 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;
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()
{
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(node->getId());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabDeckStorage::openRemoteDeckFinished);
client->sendCommand(pend);
}
}
void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer)
{
if (r.response_code() != Response::RespOk)
return;
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
const Command_DeckDownload &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDownload::ext);
DeckLoader loader;
if (!loader.loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id()))
return;
emit openDeckEditor(&loader);
}
void TabDeckStorage::actDownload()
{
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
while (!localDirModel->isDir(curLeft)) {
curLeft = curLeft.parent();
}
for (const auto curRight : serverDirView->selectionModel()->selectedRows()) {
downloadNodeAtIndex(curLeft, curRight);
}
}
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();
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, &PendingCommand::finished, this, &TabDeckStorage::downloadFinished);
client->sendCommand(pend);
}
// node at index is invalid
}
void TabDeckStorage::downloadFinished(const Response &r,
const CommandContainer & /*commandContainer*/,
const QVariant &extraData)
{
if (r.response_code() != Response::RespOk)
return;
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
QString filePath = extraData.toString();
DeckLoader deck(QString::fromStdString(resp.deck()));
deck.saveToFile(filePath, DeckLoader::CockatriceFormat);
}
void TabDeckStorage::actNewFolder()
{
QString targetPath = getTargetPath();
int max_length = MAX_NAME_LENGTH - targetPath.length() - 1; // generated length would be path + / + name
if (max_length < 1) // can't create path that's short enough
return;
QString folderName = getTextWithMax(this, tr("New folder"), tr("Name of new folder:"), max_length);
if (folderName.isEmpty())
return;
// '/' isn't a valid filename character on *nix so we're choosing to replace it with a different arbitrary
// character.
std::string folder = folderName.toStdString();
std::replace(folder.begin(), folder.end(), '/', '-');
Command_DeckNewDir cmd;
cmd.set_path(targetPath.toStdString());
cmd.set_dir_name(folder);
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabDeckStorage::newFolderFinished);
client->sendCommand(pend);
}
void TabDeckStorage::newFolderFinished(const Response &response, const CommandContainer &commandContainer)
{
if (response.response_code() != Response::RespOk)
return;
const Command_DeckNewDir &cmd = commandContainer.session_command(0).GetExtension(Command_DeckNewDir::ext);
serverDirView->addFolderToTree(QString::fromStdString(cmd.dir_name()),
serverDirView->getNodeByPath(QString::fromStdString(cmd.path())));
}
void TabDeckStorage::actDeleteRemoteDeck()
{
auto curRights = serverDirView->getCurrentSelection();
if (curRights.isEmpty()) {
return;
}
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;
if (targetPath.length() > MAX_NAME_LENGTH) {
qCritical() << "target path to delete is too long" << targetPath;
return;
}
Command_DeckDelDir cmd;
cmd.set_path(targetPath.toStdString());
pend = client->prepareSessionCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabDeckStorage::deleteFolderFinished);
} else {
const auto *deckNode = dynamic_cast<const RemoteDeckList_TreeModel::FileNode *>(curRight);
Command_DeckDel cmd;
cmd.set_deck_id(deckNode->getId());
pend = client->prepareSessionCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabDeckStorage::deleteDeckFinished);
}
client->sendCommand(pend);
}
void TabDeckStorage::deleteDeckFinished(const Response &response, const CommandContainer &commandContainer)
{
if (response.response_code() != Response::RespOk)
return;
const Command_DeckDel &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDel::ext);
RemoteDeckList_TreeModel::Node *toDelete = serverDirView->getNodeById(cmd.deck_id());
if (toDelete)
serverDirView->removeNode(toDelete);
}
void TabDeckStorage::deleteFolderFinished(const Response &response, const CommandContainer &commandContainer)
{
if (response.response_code() != Response::RespOk)
return;
const Command_DeckDelDir &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDelDir::ext);
RemoteDeckList_TreeModel::Node *toDelete = serverDirView->getNodeByPath(QString::fromStdString(cmd.path()));
if (toDelete)
serverDirView->removeNode(toDelete);
}

View file

@ -0,0 +1,93 @@
/**
* @file tab_deck_storage.h
* @ingroup DeckStorageWidgets
* @ingroup Tabs
* @brief TODO: Document this.
*/
#ifndef TAB_DECK_STORAGE_H
#define TAB_DECK_STORAGE_H
#include "../interface/widgets/server/remote/remote_decklist_tree_widget.h"
#include "tab.h"
#include <libcockatrice/network/client/abstract/abstract_client.h>
class ServerInfo_User;
class AbstractClient;
class QTreeView;
class QFileSystemModel;
class QToolBar;
class QTreeWidget;
class QTreeWidgetItem;
class QGroupBox;
class CommandContainer;
class Response;
class DeckLoader;
class TabDeckStorage : public Tab
{
Q_OBJECT
private:
AbstractClient *client;
QTreeView *localDirView;
QFileSystemModel *localDirModel;
QToolBar *leftToolBar, *rightToolBar;
RemoteDeckList_TreeWidget *serverDirView;
QGroupBox *leftGroupBox, *rightGroupBox;
QAction *aOpenLocalDeck, *aRenameLocal, *aUpload, *aNewLocalFolder, *aDeleteLocalDeck;
QAction *aOpenDecksFolder;
QAction *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck;
QString getTargetPath() const;
void setRemoteEnabled(bool enabled);
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 handleConnected(const ServerInfo_User &userInfo);
void handleConnectionChanged(ClientStatus status);
void actLocalDoubleClick(const QModelIndex &curLeft);
void actOpenLocalDeck();
void actRenameLocal();
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);
void actDownload();
void downloadFinished(const Response &r, const CommandContainer &commandContainer, const QVariant &extraData);
void actNewFolder();
void newFolderFinished(const Response &response, const CommandContainer &commandContainer);
void actDeleteRemoteDeck();
void deleteFolderFinished(const Response &response, const CommandContainer &commandContainer);
void deleteDeckFinished(const Response &response, const CommandContainer &commandContainer);
public:
TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo);
void retranslateUi() override;
QString getTabText() const override
{
return tr("Deck Storage");
}
signals:
void openDeckEditor(const DeckLoader *deckLoader);
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,205 @@
/**
* @file tab_game.h
* @ingroup Tabs
* @ingroup GameWidgets
* @ingroup Lobby
* @brief TODO: Document this.
*/
#ifndef TAB_GAME_H
#define TAB_GAME_H
#include "../game/abstract_game.h"
#include "../game/log/message_log_widget.h"
#include "../game/player/player.h"
#include "../interface/widgets/menus/tearoff_menu.h"
#include "../interface/widgets/replay/replay_manager.h"
#include "../interface/widgets/visual_deck_storage/visual_deck_storage_widget.h"
#include "tab.h"
#include <QCompleter>
#include <QLoggingCategory>
#include <QMap>
#include <libcockatrice/protocol/pb/event_leave.pb.h>
class ServerInfo_PlayerProperties;
class TabbedDeckViewContainer;
inline Q_LOGGING_CATEGORY(TabGameLog, "tab_game");
class UserListProxy;
class DeckViewContainer;
class AbstractClient;
class CardDatabase;
class GameView;
class GameScene;
class ReplayManager;
class CardInfoFrameWidget;
class QTimer;
class QSplitter;
class QLabel;
class QToolButton;
class QMenu;
class ZoneViewLayout;
class ZoneViewWidget;
class PhasesToolbar;
class PlayerListWidget;
class ReplayTimelineWidget;
class CardZone;
class AbstractCardItem;
class CardItem;
class DeckLoader;
class QVBoxLayout;
class QHBoxLayout;
class GameReplay;
class LineEditCompleter;
class QDockWidget;
class QStackedWidget;
class TabGame : public Tab
{
Q_OBJECT
private:
AbstractGame *game;
const UserListProxy *userListProxy;
ReplayManager *replayManager;
QStringList gameTypes;
QCompleter *completer;
QStringList autocompleteUserList;
QStackedWidget *mainWidget;
CardInfoFrameWidget *cardInfoFrameWidget;
PlayerListWidget *playerListWidget;
QLabel *timeElapsedLabel;
MessageLogWidget *messageLog;
QLabel *sayLabel;
LineEditCompleter *sayEdit;
PhasesToolbar *phasesToolbar;
GameScene *scene;
GameView *gameView;
QMap<int, TabbedDeckViewContainer *> deckViewContainers;
QVBoxLayout *deckViewContainerLayout;
QWidget *gamePlayAreaWidget, *deckViewContainerWidget;
QDockWidget *cardInfoDock, *messageLayoutDock, *playerListDock, *replayDock;
QAction *playersSeparator;
QMenu *gameMenu, *viewMenu, *cardInfoDockMenu, *messageLayoutDockMenu, *playerListDockMenu, *replayDockMenu;
TearOffMenu *phasesMenu;
QAction *aGameInfo, *aConcede, *aLeaveGame, *aCloseReplay, *aNextPhase, *aNextPhaseAction, *aNextTurn,
*aReverseTurn, *aRemoveLocalArrows, *aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout;
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aMessageLayoutDockVisible, *aMessageLayoutDockFloating,
*aPlayerListDockVisible, *aPlayerListDockFloating, *aReplayDockVisible, *aReplayDockFloating;
QAction *aFocusChat;
QList<QAction *> phaseActions;
QAction *aCardMenu;
Player *addPlayer(Player *newPlayer);
void addLocalPlayer(Player *newPlayer, int playerId);
void processRemotePlayerDeckSelect(QString deckList, int playerId, QString playerName);
void processMultipleRemotePlayerDeckSelect(QVector<QPair<int, QPair<QString, QString>>> playerIdDeckMap);
void processLocalPlayerDeckSelect(Player *localPlayer, int playerId, ServerInfo_Player playerInfo);
void loadDeckForLocalPlayer(Player *localPlayer, int playerId, ServerInfo_Player playerInfo);
void processLocalPlayerReady(int playerId, ServerInfo_Player playerInfo);
void createZoneForPlayer(Player *newPlayer, int playerId);
void startGame(bool resuming);
void stopGame();
void closeGame();
bool leaveGame();
Player *setActivePlayer(int id);
void setActivePhase(int phase);
void createMenuItems();
void createReplayMenuItems();
void createViewMenuItems();
void createCardInfoDock(bool bReplay = false);
void createPlayerListDock(bool bReplay = false);
void createMessageDock(bool bReplay = false);
void createPlayAreaWidget(bool bReplay = false);
void createDeckViewContainerWidget(bool bReplay = false);
void createReplayDock(GameReplay *replay);
signals:
void gameClosing(TabGame *tab);
void containerProcessingStarted(const GameEventContext &context);
void containerProcessingDone();
void openMessageDialog(const QString &userName, bool focus);
void openDeckEditor(const DeckLoader *deck);
void notIdle();
void phaseChanged(int phase);
void gameLeft();
void chatMessageSent(QString chatMessage);
void turnAdvanced();
void arrowDeletionRequested(int arrowId);
private slots:
void adminLockChanged(bool lock);
void newCardAdded(AbstractCardItem *card);
void setCardMenu(QMenu *menu);
void actGameInfo();
void actConcede();
void actRemoveLocalArrows();
void actRotateViewCW();
void actRotateViewCCW();
void actSay();
void actPhaseAction();
void actNextPhase();
void actNextPhaseAction();
void addMentionTag(const QString &value);
void linkCardToChat(const QString &cardName);
void refreshShortcuts();
void loadLayout();
void actCompleterChanged();
void notifyPlayerJoin(QString playerName);
void notifyPlayerKicked();
void processPlayerLeave(Player *leavingPlayer);
void actResetLayout();
void freeDocksSize();
void hideEvent(QHideEvent *event) override;
bool eventFilter(QObject *o, QEvent *e) override;
void dockVisibleTriggered();
void dockFloatingTriggered();
void dockTopLevelChanged(bool topLevel);
protected slots:
void closeEvent(QCloseEvent *event) override;
public:
TabGame(TabSupervisor *_tabSupervisor,
QList<AbstractClient *> &_clients,
const Event_GameJoined &event,
const QMap<int, QString> &_roomGameTypes);
void connectToGameState();
void connectToPlayerManager();
void connectToGameEventHandler();
void connectMessageLogToGameEventHandler();
void connectPlayerListToGameEventHandler();
TabGame(TabSupervisor *_tabSupervisor, GameReplay *replay);
~TabGame() override;
void retranslateUi() override;
void updatePlayerListDockTitle();
bool closeRequest() override;
QString getTabText() const override;
AbstractGame *getGame() const
{
return game;
}
public slots:
void viewCardInfo(const CardRef &cardRef = {}) const;
void resetChatAndPhase();
void updateTimeElapsedLabel(QString newTime);
void addPlayerToAutoCompleteList(QString playerName);
void removePlayerFromAutoCompleteList(QString playerName);
void removeSpectator(int spectatorId, ServerInfo_User spectator);
void processLocalPlayerSideboardLocked(int playerId, bool sideboardLocked);
void processLocalPlayerReadyStateChanged(int playerId, bool ready);
void emitUserEvent();
};
#endif

View file

@ -0,0 +1,14 @@
#include "tab_home.h"
#include <QGroupBox>
#include <QPushButton>
TabHome::TabHome(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
{
homeWidget = new HomeWidget(this, tabSupervisor);
setCentralWidget(homeWidget);
}
void TabHome::retranslateUi()
{
}

View file

@ -0,0 +1,35 @@
/**
* @file tab_home.h
* @ingroup Tabs
* @brief TODO: Document this.
*/
#ifndef TAB_HOME_H
#define TAB_HOME_H
#include "../interface/widgets/general/home_widget.h"
#include "tab.h"
#include <QHBoxLayout>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <qgroupbox.h>
class AbstractClient;
class TabHome : public Tab
{
Q_OBJECT
private:
AbstractClient *client;
HomeWidget *homeWidget;
public:
TabHome(TabSupervisor *_tabSupervisor, AbstractClient *_client);
void retranslateUi() override;
QString getTabText() const override
{
return tr("Home");
}
};
#endif // TAB_HOME_H

View file

@ -0,0 +1,348 @@
#include "tab_logs.h"
#include "../dialogs/dlg_manage_sets.h"
#include "../interface/widgets/utility/custom_line_edit.h"
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QRadioButton>
#include <QSpinBox>
#include <QTabWidget>
#include <QtGui>
#include <QtWidgets>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
#include <libcockatrice/protocol/pb/response_viewlog_history.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/trice_limits.h>
TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
{
roomTable = new QTableWidget();
roomTable->setColumnCount(6);
roomTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
roomTable->setHorizontalHeaderLabels(
QString(tr("Time;SenderName;SenderIP;Message;TargetID;TargetName")).split(";"));
gameTable = new QTableWidget();
gameTable->setColumnCount(6);
gameTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
gameTable->setHorizontalHeaderLabels(
QString(tr("Time;SenderName;SenderIP;Message;TargetID;TargetName")).split(";"));
chatTable = new QTableWidget();
chatTable->setColumnCount(6);
chatTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
chatTable->setHorizontalHeaderLabels(
QString(tr("Time;SenderName;SenderIP;Message;TargetID;TargetName")).split(";"));
QTabWidget *tabManager = new QTabWidget();
tabManager->addTab(roomTable, tr("Room Logs"));
tabManager->addTab(gameTable, tr("Game Logs"));
tabManager->addTab(chatTable, tr("Chat Logs"));
setCentralWidget(tabManager);
createDock();
restartLayout();
clearClicked();
retranslateUi();
}
TabLog::~TabLog()
{
}
void TabLog::retranslateUi()
{
}
void TabLog::getClicked()
{
if (findUsername->text().isEmpty() && findIPAddress->text().isEmpty() && findGameName->text().isEmpty() &&
findGameID->text().isEmpty() && findMessage->text().isEmpty()) {
QMessageBox::critical(this, tr("Error"), tr("You must select at least one filter."));
return;
}
if (!lastHour->isChecked() && !today->isChecked() && !pastDays->isChecked()) {
pastDays->setChecked(true);
pastXDays->setValue(20);
}
if (pastDays->isChecked() && pastXDays->value() == 0) {
QMessageBox::critical(this, tr("Error"), tr("You have to select a valid number of days to locate."));
return;
}
if (!mainRoom->isChecked() && !gameRoom->isChecked() && !privateChat->isChecked()) {
mainRoom->setChecked(true);
gameRoom->setChecked(true);
privateChat->setChecked(true);
}
if (maximumResults->value() == 0)
maximumResults->setValue(1000);
int dateRange = 0;
if (lastHour->isChecked())
dateRange = 1;
if (today->isChecked())
dateRange = 24;
if (pastDays->isChecked())
dateRange = pastXDays->value() * 24;
Command_ViewLogHistory cmd;
cmd.set_user_name(findUsername->text().toStdString());
cmd.set_ip_address(findIPAddress->text().toStdString());
cmd.set_game_name(findGameName->text().toStdString());
cmd.set_game_id(findGameID->text().toStdString());
cmd.set_message(findMessage->text().toStdString());
if (mainRoom->isChecked()) {
cmd.add_log_location("room");
};
if (gameRoom->isChecked()) {
cmd.add_log_location("game");
};
if (privateChat->isChecked()) {
cmd.add_log_location("chat");
};
cmd.set_date_range(dateRange);
cmd.set_maximum_results(maximumResults->value());
PendingCommand *pend = client->prepareModeratorCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabLog::viewLogHistory_processResponse);
client->sendCommand(pend);
}
void TabLog::clearClicked()
{
findUsername->clear();
findIPAddress->clear();
findGameName->clear();
findGameID->clear();
findMessage->clear();
pastXDays->clear();
maximumResults->clear();
mainRoom->setChecked(false);
gameRoom->setChecked(false);
privateChat->setChecked(false);
pastDays->setAutoExclusive(false);
pastDays->setChecked(false);
today->setAutoExclusive(false);
today->setChecked(false);
lastHour->setAutoExclusive(false);
lastHour->setChecked(false);
pastDays->setAutoExclusive(true);
today->setAutoExclusive(true);
lastHour->setAutoExclusive(true);
}
void TabLog::createDock()
{
labelFindUserName = new QLabel(tr("Username: "));
findUsername = new LineEditUnfocusable("");
findUsername->setMaxLength(MAX_NAME_LENGTH);
findUsername->setAlignment(Qt::AlignCenter);
labelFindIPAddress = new QLabel(tr("IP Address: "));
findIPAddress = new LineEditUnfocusable("");
findIPAddress->setMaxLength(MAX_NAME_LENGTH);
findIPAddress->setAlignment(Qt::AlignCenter);
labelFindGameName = new QLabel(tr("Game Name: "));
findGameName = new LineEditUnfocusable("");
findGameName->setMaxLength(MAX_NAME_LENGTH);
findGameName->setAlignment(Qt::AlignCenter);
labelFindGameID = new QLabel(tr("GameID: "));
findGameID = new LineEditUnfocusable("");
findGameID->setMaxLength(MAX_NAME_LENGTH);
findGameID->setAlignment(Qt::AlignCenter);
labelMessage = new QLabel(tr("Message: "));
findMessage = new LineEditUnfocusable("");
findMessage->setMaxLength(MAX_TEXT_LENGTH);
findMessage->setAlignment(Qt::AlignCenter);
mainRoom = new QCheckBox(tr("Main Room"));
gameRoom = new QCheckBox(tr("Game Room"));
privateChat = new QCheckBox(tr("Private Chat"));
pastDays = new QRadioButton(tr("Past X Days: "));
today = new QRadioButton(tr("Today"));
lastHour = new QRadioButton(tr("Last Hour"));
pastXDays = new QSpinBox;
pastXDays->setMaximum(20);
labelMaximum = new QLabel(tr("Maximum Results: "));
maximumResults = new QSpinBox;
maximumResults->setMaximum(1000);
labelDescription = new QLabel(tr(
"At least one filter is required.\nThe more information you put in, the more specific your results will be."));
getButton = new QPushButton(tr("Get User Logs"));
getButton->setAutoDefault(true);
connect(getButton, &QPushButton::clicked, this, &TabLog::getClicked);
clearButton = new QPushButton(tr("Clear Filters"));
clearButton->setAutoDefault(true);
connect(clearButton, &QPushButton::clicked, this, &TabLog::clearClicked);
criteriaGrid = new QGridLayout;
criteriaGrid->addWidget(labelFindUserName, 0, 0);
criteriaGrid->addWidget(findUsername, 0, 1);
criteriaGrid->addWidget(labelFindIPAddress, 1, 0);
criteriaGrid->addWidget(findIPAddress, 1, 1);
criteriaGrid->addWidget(labelFindGameName, 2, 0);
criteriaGrid->addWidget(findGameName, 2, 1);
criteriaGrid->addWidget(labelFindGameID, 3, 0);
criteriaGrid->addWidget(findGameID, 3, 1);
criteriaGrid->addWidget(labelMessage, 4, 0);
criteriaGrid->addWidget(findMessage, 4, 1);
criteriaGroupBox = new QGroupBox(tr("Filters"));
criteriaGroupBox->setLayout(criteriaGrid);
criteriaGroupBox->setMaximumSize(500, 300);
criteriaGroupBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
locationGrid = new QGridLayout;
locationGrid->addWidget(mainRoom, 0, 0);
locationGrid->addWidget(gameRoom, 0, 1);
locationGrid->addWidget(privateChat, 0, 2);
locationGroupBox = new QGroupBox(tr("Log Locations"));
locationGroupBox->setLayout(locationGrid);
rangeGrid = new QGridLayout;
rangeGrid->addWidget(pastDays, 0, 0);
rangeGrid->addWidget(pastXDays, 0, 1);
rangeGrid->addWidget(today, 0, 2);
rangeGrid->addWidget(lastHour, 0, 3);
rangeGroupBox = new QGroupBox(tr("Date Range"));
rangeGroupBox->setLayout(rangeGrid);
maxResultsGrid = new QGridLayout;
maxResultsGrid->addWidget(labelMaximum, 0, 0);
maxResultsGrid->addWidget(maximumResults, 0, 1);
maxResultsGroupBox = new QGroupBox(tr("Maximum Results"));
maxResultsGroupBox->setLayout(maxResultsGrid);
descriptionGrid = new QGridLayout;
descriptionGrid->addWidget(labelDescription, 0, 0);
descriptionGroupBox = new QGroupBox(tr(""));
descriptionGroupBox->setLayout(descriptionGrid);
buttonGrid = new QGridLayout;
buttonGrid->addWidget(getButton, 0, 0);
buttonGrid->addWidget(clearButton, 0, 1);
buttonGroupBox = new QGroupBox(tr(""));
buttonGroupBox->setLayout(buttonGrid);
mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(criteriaGroupBox);
mainLayout->addWidget(locationGroupBox);
mainLayout->addWidget(rangeGroupBox);
mainLayout->addWidget(maxResultsGroupBox);
mainLayout->addWidget(descriptionGroupBox);
mainLayout->addWidget(buttonGroupBox);
mainLayout->setAlignment(Qt::AlignCenter);
searchDockContents = new QWidget(this);
searchDockContents->setLayout(mainLayout);
searchDock = new QDockWidget(this);
searchDock->setFeatures(QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
searchDock->setWidget(searchDockContents);
}
void TabLog::viewLogHistory_processResponse(const Response &resp)
{
const Response_ViewLogHistory &response = resp.GetExtension(Response_ViewLogHistory::ext);
if (resp.response_code() != Response::RespOk) {
QMessageBox::critical(static_cast<QWidget *>(parent()), tr("Message History"),
tr("Failed to collect message history information."));
return;
}
if (response.log_message_size() == 0) {
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Message History"),
tr("There are no messages for the selected filters."));
return;
}
int roomCounter = 0, gameCounter = 0, chatCounter = 0;
roomTable->setRowCount(roomCounter);
gameTable->setRowCount(gameCounter);
chatTable->setRowCount(chatCounter);
for (int i = 0; i < response.log_message_size(); ++i) {
ServerInfo_ChatMessage message = response.log_message(i);
if (QString::fromStdString(message.target_type()) == "room") {
roomTable->insertRow(roomCounter);
roomTable->setItem(roomCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time())));
roomTable->setItem(roomCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name())));
roomTable->setItem(roomCounter, 2, new QTableWidgetItem(QString::fromStdString(message.sender_ip())));
roomTable->setItem(roomCounter, 3, new QTableWidgetItem(QString::fromStdString(message.message())));
roomTable->setItem(roomCounter, 4, new QTableWidgetItem(QString::fromStdString(message.target_id())));
roomTable->setItem(roomCounter, 5, new QTableWidgetItem(QString::fromStdString(message.target_name())));
++roomCounter;
}
if (QString::fromStdString(message.target_type()) == "game") {
gameTable->insertRow(gameCounter);
gameTable->setItem(gameCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time())));
gameTable->setItem(gameCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name())));
gameTable->setItem(gameCounter, 2, new QTableWidgetItem(QString::fromStdString(message.sender_ip())));
gameTable->setItem(gameCounter, 3, new QTableWidgetItem(QString::fromStdString(message.message())));
gameTable->setItem(gameCounter, 4, new QTableWidgetItem(QString::fromStdString(message.target_id())));
gameTable->setItem(gameCounter, 5, new QTableWidgetItem(QString::fromStdString(message.target_name())));
++gameCounter;
}
if (QString::fromStdString(message.target_type()) == "chat") {
chatTable->insertRow(chatCounter);
chatTable->setItem(chatCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time())));
chatTable->setItem(chatCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name())));
chatTable->setItem(chatCounter, 2, new QTableWidgetItem(QString::fromStdString(message.sender_ip())));
chatTable->setItem(chatCounter, 3, new QTableWidgetItem(QString::fromStdString(message.message())));
chatTable->setItem(chatCounter, 4, new QTableWidgetItem(QString::fromStdString(message.target_id())));
chatTable->setItem(chatCounter, 5, new QTableWidgetItem(QString::fromStdString(message.target_name())));
++chatCounter;
}
}
if (roomCounter) {
roomTable->show();
roomTable->resizeColumnsToContents();
} else {
roomTable->hide();
}
if (gameCounter) {
gameTable->resizeColumnsToContents();
gameTable->show();
} else {
gameTable->hide();
}
if (chatCounter) {
chatTable->resizeColumnsToContents();
chatTable->show();
} else {
chatTable->hide();
}
}
void TabLog::restartLayout()
{
searchDock->setFloating(false);
addDockWidget(Qt::LeftDockWidgetArea, searchDock);
searchDock->setVisible(true);
}

View file

@ -0,0 +1,71 @@
/**
* @file tab_logs.h
* @ingroup ServerTabs
* @brief TODO: Document this.
*/
#ifndef TAB_LOG_H
#define TAB_LOG_H
#include "tab.h"
#include <QDialog>
class AbstractClient;
class LineEditUnfocusable;
class QGroupBox;
class QPushButton;
class QSpinBox;
class QCheckBox;
class QRadioButton;
class QLabel;
class QDockWidget;
class QWidget;
class QGridLayout;
class QVBoxLayout;
class QTableWidget;
class CommandContainer;
class Response;
class AbstractClient;
class TabLog : public Tab
{
Q_OBJECT
private:
AbstractClient *client;
QLabel *labelFindUserName, *labelFindIPAddress, *labelFindGameName, *labelFindGameID, *labelMessage, *labelMaximum,
*labelDescription;
LineEditUnfocusable *findUsername, *findIPAddress, *findGameName, *findGameID, *findMessage;
QCheckBox *mainRoom, *gameRoom, *privateChat;
QRadioButton *pastDays, *today, *lastHour;
QSpinBox *maximumResults, *pastXDays;
QDockWidget *searchDock;
QWidget *searchDockContents;
QPushButton *getButton, *clearButton;
QGridLayout *criteriaGrid, *locationGrid, *rangeGrid, *maxResultsGrid, *descriptionGrid, *buttonGrid;
QGroupBox *criteriaGroupBox, *locationGroupBox, *rangeGroupBox, *maxResultsGroupBox, *descriptionGroupBox,
*buttonGroupBox;
QVBoxLayout *mainLayout;
QTableWidget *roomTable, *gameTable, *chatTable;
void createDock();
signals:
private slots:
void getClicked();
void clearClicked();
void viewLogHistory_processResponse(const Response &resp);
void restartLayout();
public:
TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client);
~TabLog() override;
void retranslateUi() override;
QString getTabText() const override
{
return tr("Logs");
}
};
#endif

View file

@ -0,0 +1,170 @@
#include "tab_message.h"
#include "../client/sound_engine.h"
#include "../interface/widgets/server/chat_view/chat_view.h"
#include "../interface/widgets/server/user/user_list_manager.h"
#include "../interface/widgets/utility/custom_line_edit.h"
#include "../main.h"
#include <QApplication>
#include <QDebug>
#include <QMenu>
#include <QSystemTrayIcon>
#include <QVBoxLayout>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pb/event_user_message.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/protocol/pb/session_commands.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/settings/cache_settings.h>
#include <libcockatrice/utility/trice_limits.h>
TabMessage::TabMessage(TabSupervisor *_tabSupervisor,
AbstractClient *_client,
const ServerInfo_User &_ownUserInfo,
const ServerInfo_User &_otherUserInfo)
: Tab(_tabSupervisor), client(_client), ownUserInfo(new ServerInfo_User(_ownUserInfo)),
otherUserInfo(new ServerInfo_User(_otherUserInfo)), userOnline(true)
{
chatView = new ChatView(tabSupervisor, 0, true);
connect(chatView, &ChatView::showCardInfoPopup, this, &TabMessage::showCardInfoPopup);
connect(chatView, &ChatView::deleteCardInfoPopup, this, &TabMessage::deleteCardInfoPopup);
connect(chatView, &ChatView::addMentionTag, this, &TabMessage::addMentionTag);
sayEdit = new LineEditUnfocusable;
sayEdit->setMaxLength(MAX_TEXT_LENGTH);
connect(sayEdit, &LineEditUnfocusable::returnPressed, this, &TabMessage::sendMessage);
QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(chatView);
vbox->addWidget(sayEdit);
aLeave = new QAction(this);
connect(aLeave, &QAction::triggered, this, &TabMessage::closeRequest);
messageMenu = new QMenu(this);
messageMenu->addAction(aLeave);
addTabMenu(messageMenu);
retranslateUi();
QWidget *mainWidget = new QWidget(this);
mainWidget->setLayout(vbox);
setCentralWidget(mainWidget);
}
TabMessage::~TabMessage()
{
delete ownUserInfo;
delete otherUserInfo;
}
void TabMessage::addMentionTag(QString mentionTag)
{
sayEdit->insert(mentionTag + " ");
sayEdit->setFocus();
}
void TabMessage::retranslateUi()
{
messageMenu->setTitle(tr("Private &chat"));
aLeave->setText(tr("&Leave"));
}
void TabMessage::tabActivated()
{
if (!sayEdit->hasFocus())
sayEdit->setFocus();
}
QString TabMessage::getUserName() const
{
return QString::fromStdString(otherUserInfo->name());
}
QString TabMessage::getTabText() const
{
return tr("%1 - Private chat").arg(QString::fromStdString(otherUserInfo->name()));
}
void TabMessage::closeEvent(QCloseEvent *event)
{
emit talkClosing(this);
event->accept();
}
void TabMessage::sendMessage()
{
if (sayEdit->text().isEmpty() || !userOnline)
return;
Command_Message cmd;
cmd.set_user_name(otherUserInfo->name());
cmd.set_message(sayEdit->text().toStdString());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabMessage::messageSent);
client->sendCommand(pend);
sayEdit->clear();
}
void TabMessage::messageSent(const Response &response)
{
if (response.response_code() == Response::RespInIgnoreList)
chatView->appendMessage(tr(
"This user is ignoring you, they cannot see your messages in main chat and you cannot join their games."));
}
void TabMessage::processUserMessageEvent(const Event_UserMessage &event)
{
auto userInfo = event.sender_name() == otherUserInfo->name() ? otherUserInfo : ownUserInfo;
chatView->appendMessage(QString::fromStdString(event.message()), {}, *userInfo, true);
if (tabSupervisor->currentIndex() != tabSupervisor->indexOf(this))
soundEngine->playSound("private_message");
if (SettingsCache::instance().getShowMessagePopup() && shouldShowSystemPopup(event))
showSystemPopup(event);
if (QString::fromStdString(event.sender_name()).toLower().simplified() == "servatrice")
sayEdit->setDisabled(true);
emit userEvent();
}
bool TabMessage::shouldShowSystemPopup(const Event_UserMessage &event)
{
return (QApplication::activeWindow() == 0 || QApplication::focusWidget() == 0 ||
(event.sender_name() == otherUserInfo->name() &&
tabSupervisor->currentIndex() != tabSupervisor->indexOf(this)));
}
void TabMessage::showSystemPopup(const Event_UserMessage &event)
{
if (trayIcon) {
disconnect(trayIcon, &QSystemTrayIcon::messageClicked, 0, 0);
trayIcon->showMessage(tr("Private message from") + " " + otherUserInfo->name().c_str(),
event.message().c_str());
connect(trayIcon, &QSystemTrayIcon::messageClicked, this, &TabMessage::messageClicked);
} else {
qCWarning(TabMessageLog) << "Error: trayIcon is NULL. TabMessage::showSystemPopup failed";
}
}
void TabMessage::messageClicked()
{
tabSupervisor->setCurrentIndex(tabSupervisor->indexOf(this));
activateWindow();
emit maximizeClient();
}
void TabMessage::processUserLeft()
{
chatView->appendMessage(tr("%1 has left the server.").arg(QString::fromStdString(otherUserInfo->name())));
userOnline = false;
}
void TabMessage::processUserJoined(const ServerInfo_User &_userInfo)
{
chatView->appendMessage(tr("%1 has joined the server.").arg(QString::fromStdString(otherUserInfo->name())));
userOnline = true;
*otherUserInfo = _userInfo;
}

View file

@ -0,0 +1,70 @@
/**
* @file tab_message.h
* @ingroup MessagingTabs
* @brief TODO: Document this.
*/
#ifndef TAB_MESSAGE_H
#define TAB_MESSAGE_H
#include "tab.h"
#include <QLoggingCategory>
inline Q_LOGGING_CATEGORY(TabMessageLog, "tab_message");
class AbstractClient;
class ChatView;
class LineEditUnfocusable;
class Event_UserMessage;
class Response;
class ServerInfo_User;
class TabMessage : public Tab
{
Q_OBJECT
private:
AbstractClient *client;
QMenu *messageMenu;
ServerInfo_User *ownUserInfo;
ServerInfo_User *otherUserInfo;
bool userOnline;
ChatView *chatView;
LineEditUnfocusable *sayEdit;
QAction *aLeave;
signals:
void talkClosing(TabMessage *tab);
void maximizeClient();
private slots:
void sendMessage();
void messageSent(const Response &response);
void addMentionTag(QString mentionTag);
void messageClicked();
protected slots:
void closeEvent(QCloseEvent *event) override;
public:
TabMessage(TabSupervisor *_tabSupervisor,
AbstractClient *_client,
const ServerInfo_User &_ownUserInfo,
const ServerInfo_User &_otherUserInfo);
~TabMessage() override;
void retranslateUi() override;
void tabActivated() override;
QString getUserName() const;
QString getTabText() const override;
void processUserMessageEvent(const Event_UserMessage &event);
void processUserLeft();
void processUserJoined(const ServerInfo_User &_userInfo);
private:
bool shouldShowSystemPopup(const Event_UserMessage &event);
void showSystemPopup(const Event_UserMessage &event);
};
#endif

View file

@ -0,0 +1,619 @@
#include "tab_replays.h"
#include "../interface/widgets/server/remote/remote_replay_list_tree_widget.h"
#include "tab_game.h"
#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QDesktopServices>
#include <QFileSystemModel>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QInputDialog>
#include <QMessageBox>
#include <QToolBar>
#include <QTreeView>
#include <QUrl>
#include <QVBoxLayout>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pb/command_replay_delete_match.pb.h>
#include <libcockatrice/protocol/pb/command_replay_download.pb.h>
#include <libcockatrice/protocol/pb/command_replay_get_code.pb.h>
#include <libcockatrice/protocol/pb/command_replay_modify_match.pb.h>
#include <libcockatrice/protocol/pb/command_replay_submit_code.pb.h>
#include <libcockatrice/protocol/pb/event_replay_added.pb.h>
#include <libcockatrice/protocol/pb/game_replay.pb.h>
#include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/protocol/pb/response_replay_download.pb.h>
#include <libcockatrice/protocol/pb/response_replay_get_code.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/settings/cache_settings.h>
TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo)
: Tab(_tabSupervisor), client(_client)
{
leftGroupBox = createLeftLayout();
rightGroupBox = createRightLayout();
// combine layouts
QHBoxLayout *hbox = new QHBoxLayout;
hbox->addWidget(leftGroupBox);
hbox->addWidget(rightGroupBox);
retranslateUi();
QWidget *mainWidget = new QWidget(this);
mainWidget->setLayout(hbox);
setCentralWidget(mainWidget);
connect(client, &AbstractClient::replayAddedEventReceived, this, &TabReplays::replayAddedEventReceived);
connect(client, &AbstractClient::userInfoChanged, this, &TabReplays::handleConnected);
connect(client, &AbstractClient::statusChanged, this, &TabReplays::handleConnectionChanged);
setRemoteEnabled(currentUserInfo && currentUserInfo->user_level() & ServerInfo_User::IsRegistered);
}
QGroupBox *TabReplays::createLeftLayout()
{
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);
QToolBar *toolBar = new QToolBar(this);
toolBar->setOrientation(Qt::Horizontal);
toolBar->setIconSize(QSize(32, 32));
QToolBar *rightmostToolBar = new QToolBar(this);
rightmostToolBar->setOrientation(Qt::Horizontal);
rightmostToolBar->setIconSize(QSize(32, 32));
QGridLayout *toolBarLayout = new QGridLayout;
toolBarLayout->addWidget(dummyToolBar, 0, 0, Qt::AlignLeft);
toolBarLayout->addWidget(toolBar, 0, 1, Qt::AlignHCenter);
toolBarLayout->addWidget(rightmostToolBar, 0, 2, Qt::AlignRight);
QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(localDirView);
vbox->addLayout(toolBarLayout);
QGroupBox *groupBox = new QGroupBox;
groupBox->setLayout(vbox);
// Left side actions
aOpenLocalReplay = new QAction(this);
aOpenLocalReplay->setIcon(QPixmap("theme:icons/view"));
connect(aOpenLocalReplay, &QAction::triggered, this, &TabReplays::actOpenLocalReplay);
connect(localDirView, &QTreeView::doubleClicked, this, &TabReplays::actOpenLocalReplay);
aRenameLocal = new QAction(this);
aRenameLocal->setIcon(QPixmap("theme:icons/rename"));
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, &QAction::triggered, this, &TabReplays::actDeleteLocalReplay);
aOpenReplaysFolder = new QAction(this);
aOpenReplaysFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_DirOpenIcon));
connect(aOpenReplaysFolder, &QAction::triggered, this, &TabReplays::actOpenReplaysFolder);
// Add actions to toolbars
toolBar->addAction(aOpenLocalReplay);
toolBar->addAction(aRenameLocal);
toolBar->addAction(aNewLocalFolder);
toolBar->addAction(aDeleteLocalReplay);
rightmostToolBar->addAction(aOpenReplaysFolder);
return groupBox;
}
QGroupBox *TabReplays::createRightLayout()
{
serverDirView = new RemoteReplayList_TreeWidget(client);
// Right 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);
QToolBar *toolBar = new QToolBar(this);
toolBar->setOrientation(Qt::Horizontal);
toolBar->setIconSize(QSize(32, 32));
QToolBar *rightmostToolBar = new QToolBar(this);
rightmostToolBar->setOrientation(Qt::Horizontal);
rightmostToolBar->setIconSize(QSize(32, 32));
QGridLayout *toolBarLayout = new QGridLayout;
toolBarLayout->addWidget(dummyToolBar, 0, 0, Qt::AlignLeft);
toolBarLayout->addWidget(toolBar, 0, 1, Qt::AlignHCenter);
toolBarLayout->addWidget(rightmostToolBar, 0, 2, Qt::AlignRight);
QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(serverDirView);
vbox->addLayout(toolBarLayout);
QGroupBox *groupBox = new QGroupBox;
groupBox->setLayout(vbox);
// Right side actions
aOpenRemoteReplay = new QAction(this);
aOpenRemoteReplay->setIcon(QPixmap("theme:icons/view"));
connect(aOpenRemoteReplay, &QAction::triggered, this, &TabReplays::actOpenRemoteReplay);
connect(serverDirView, &QTreeView::doubleClicked, this, &TabReplays::actOpenRemoteReplay);
aDownload = new QAction(this);
aDownload->setIcon(QPixmap("theme:icons/arrow_left_green"));
connect(aDownload, &QAction::triggered, this, &TabReplays::actDownload);
aKeep = new QAction(this);
aKeep->setIcon(QPixmap("theme:icons/lock"));
connect(aKeep, &QAction::triggered, this, &TabReplays::actKeepRemoteReplay);
aDeleteRemoteReplay = new QAction(this);
aDeleteRemoteReplay->setIcon(QPixmap("theme:icons/remove_row"));
connect(aDeleteRemoteReplay, &QAction::triggered, this, &TabReplays::actDeleteRemoteReplay);
aGetReplayCode = new QAction(this);
aGetReplayCode->setIcon(QPixmap("theme:icons/share"));
connect(aGetReplayCode, &QAction::triggered, this, &TabReplays::actGetReplayCode);
aSubmitReplayCode = new QAction(this);
aSubmitReplayCode->setIcon(QPixmap("theme:icons/search"));
connect(aSubmitReplayCode, &QAction::triggered, this, &TabReplays::actSubmitReplayCode);
// Add actions to toolbars
toolBar->addAction(aOpenRemoteReplay);
toolBar->addAction(aDownload);
toolBar->addAction(aKeep);
toolBar->addAction(aDeleteRemoteReplay);
toolBar->addAction(aGetReplayCode);
rightmostToolBar->addAction(aSubmitReplayCode);
return groupBox;
}
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"));
aGetReplayCode->setText(tr("Get replay share code"));
aSubmitReplayCode->setText(tr("Look up replay by share code"));
}
void TabReplays::handleConnected(const ServerInfo_User &userInfo)
{
setRemoteEnabled(userInfo.user_level() & ServerInfo_User::IsRegistered);
}
/**
* This is only responsible for handling the disconnect. The connect is already handled elsewhere
*/
void TabReplays::handleConnectionChanged(ClientStatus status)
{
if (status == StatusDisconnected) {
setRemoteEnabled(false);
}
}
void TabReplays::setRemoteEnabled(bool enabled)
{
aOpenRemoteReplay->setEnabled(enabled);
aDownload->setEnabled(enabled);
aKeep->setEnabled(enabled);
aDeleteRemoteReplay->setEnabled(enabled);
aGetReplayCode->setEnabled(enabled);
aSubmitReplayCode->setEnabled(enabled);
if (enabled) {
serverDirView->refreshTree();
} else {
serverDirView->clearTree();
}
}
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, &PendingCommand::finished, this, &TabReplays::openRemoteReplayFinished);
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, &PendingCommand::finished, this, &TabReplays::downloadFinished);
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, &PendingCommand::finished, this, &TabReplays::keepRemoteReplayFinished);
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, &PendingCommand::finished, this, &TabReplays::deleteRemoteReplayFinished);
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::actGetReplayCode()
{
const auto curRights = serverDirView->getSelectedReplayMatches();
if (curRights.isEmpty()) {
return;
}
for (const auto curRight : curRights) {
Command_ReplayGetCode cmd;
cmd.set_game_id(curRight->game_id());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabReplays::getReplayCodeFinished);
client->sendCommand(pend);
}
}
void TabReplays::getReplayCodeFinished(const Response &r, const CommandContainer & /*commandContainer*/)
{
if (r.response_code() == Response::RespFunctionNotAllowed) {
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Warning);
msgBox.setText(tr("Failed to get code"));
msgBox.setInformativeText(
tr("Either this server does not support replay sharing, or does not permit replay sharing for you."));
msgBox.exec();
return;
}
if (r.response_code() != Response::RespOk) {
QMessageBox::warning(this, tr("Failed"), tr("Could not get replay code"));
return;
}
const Response_ReplayGetCode &resp = r.GetExtension(Response_ReplayGetCode::ext);
QString code = QString::fromStdString(resp.replay_code());
QMessageBox msgBox;
msgBox.setText(tr("Replay Share Code"));
msgBox.setInformativeText(
tr("Others can use this code to add the replay to their list of remote replays:\n%1").arg(code));
msgBox.setStandardButtons(QMessageBox::Ok);
QPushButton *copyToClipboardButton = msgBox.addButton(tr("Copy to clipboard"), QMessageBox::ActionRole);
connect(copyToClipboardButton, &QPushButton::clicked, this, [code] { QApplication::clipboard()->setText(code); });
msgBox.setDefaultButton(copyToClipboardButton);
msgBox.exec();
}
void TabReplays::actSubmitReplayCode()
{
bool ok;
QString code = QInputDialog::getText(this, tr("Look up replay by share code"), tr("Replay share code"),
QLineEdit::Normal, "", &ok);
if (!ok) {
return;
}
Command_ReplaySubmitCode cmd;
cmd.set_replay_code(code.toStdString());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabReplays::submitReplayCodeFinished);
client->sendCommand(pend);
}
void TabReplays::submitReplayCodeFinished(const Response &r, const CommandContainer & /*commandContainer*/)
{
switch (r.response_code()) {
case Response::RespOk: {
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Information);
msgBox.setText(tr("Replay code found"));
msgBox.setInformativeText(tr("Replay was added, or you already had access to it."));
msgBox.exec();
break;
}
case Response::RespNameNotFound:
QMessageBox::warning(this, tr("Failed"), tr("Replay code not found"));
break;
case Response::RespFunctionNotAllowed: {
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Warning);
msgBox.setText(tr("Failed to submit code"));
msgBox.setInformativeText(
tr("Either this server does not support replay sharing, or does not permit replay sharing for you."));
msgBox.exec();
break;
}
default:
QMessageBox::warning(this, tr("Failed"), tr("Unexpected error"));
break;
}
}
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 or a user submits a replay code, we need to refresh their view
serverDirView->refreshTree();
}
}

View file

@ -0,0 +1,93 @@
/**
* @file tab_replays.h
* @ingroup Replays
* @ingroup Tabs
* @brief TODO: Document this.
*/
#ifndef TAB_REPLAYS_H
#define TAB_REPLAYS_H
#include "tab.h"
#include <libcockatrice/network/client/abstract/abstract_client.h>
class ServerInfo_User;
class Response;
class AbstractClient;
class QTreeView;
class QFileSystemModel;
class QToolBar;
class QGroupBox;
class RemoteReplayList_TreeWidget;
class GameReplay;
class Event_ReplayAdded;
class CommandContainer;
class TabReplays : public Tab
{
Q_OBJECT
private:
AbstractClient *client;
QTreeView *localDirView;
QFileSystemModel *localDirModel;
RemoteReplayList_TreeWidget *serverDirView;
QGroupBox *leftGroupBox, *rightGroupBox;
QAction *aOpenLocalReplay, *aRenameLocal, *aNewLocalFolder, *aDeleteLocalReplay;
QAction *aOpenReplaysFolder;
QAction *aOpenRemoteReplay, *aDownload, *aKeep, *aDeleteRemoteReplay, *aGetReplayCode;
QAction *aSubmitReplayCode;
QGroupBox *createLeftLayout();
QGroupBox *createRightLayout();
void setRemoteEnabled(bool enabled);
void downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight);
private slots:
void handleConnected(const ServerInfo_User &userInfo);
void handleConnectionChanged(ClientStatus status);
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);
void actDownload();
void downloadFinished(const Response &r, const CommandContainer &commandContainer, const QVariant &extraData);
void actKeepRemoteReplay();
void keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer);
void actDeleteRemoteReplay();
void deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer);
void actGetReplayCode();
void getReplayCodeFinished(const Response &r, const CommandContainer &commandContainer);
void actSubmitReplayCode();
void submitReplayCodeFinished(const Response &r, const CommandContainer &commandContainer);
void replayAddedEventReceived(const Event_ReplayAdded &event);
signals:
void openReplay(GameReplay *replay);
public:
TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo);
void retranslateUi() override;
QString getTabText() const override
{
return tr("Game Replays");
}
};
#endif

View file

@ -0,0 +1,338 @@
#include "tab_room.h"
#include "../dialogs/dlg_settings.h"
#include "../interface/widgets/server/chat_view/chat_view.h"
#include "../interface/widgets/server/game_selector.h"
#include "../interface/widgets/server/user/user_list_manager.h"
#include "../interface/widgets/server/user/user_list_widget.h"
#include "../main.h"
#include "tab_account.h"
#include "tab_supervisor.h"
#include <QApplication>
#include <QCompleter>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QPushButton>
#include <QSplitter>
#include <QSystemTrayIcon>
#include <QToolButton>
#include <QVBoxLayout>
#include <QtCore/qdatetime.h>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/get_pb_extension.h>
#include <libcockatrice/protocol/pb/event_join_room.pb.h>
#include <libcockatrice/protocol/pb/event_leave_room.pb.h>
#include <libcockatrice/protocol/pb/event_list_games.pb.h>
#include <libcockatrice/protocol/pb/event_remove_messages.pb.h>
#include <libcockatrice/protocol/pb/event_room_say.pb.h>
#include <libcockatrice/protocol/pb/room_commands.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_room.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/settings/cache_settings.h>
#include <libcockatrice/utility/trice_limits.h>
TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
AbstractClient *_client,
ServerInfo_User *_ownUser,
const ServerInfo_Room &info)
: Tab(_tabSupervisor), client(_client), roomId(info.room_id()), roomName(QString::fromStdString(info.name())),
ownUser(_ownUser), userListProxy(_tabSupervisor->getUserListManager())
{
const int gameTypeListSize = info.gametype_list_size();
for (int i = 0; i < gameTypeListSize; ++i)
gameTypes.insert(info.gametype_list(i).game_type_id(),
QString::fromStdString(info.gametype_list(i).description()));
QMap<int, GameTypeMap> tempMap;
tempMap.insert(info.room_id(), gameTypes);
gameSelector = new GameSelector(client, tabSupervisor, this, QMap<int, QString>(), tempMap, true, true);
userList = new UserListWidget(tabSupervisor, client, UserListWidget::RoomList);
connect(userList, SIGNAL(openMessageDialog(const QString &, bool)), this,
SIGNAL(openMessageDialog(const QString &, bool)));
chatView = new ChatView(tabSupervisor, nullptr, true, this);
connect(chatView, &ChatView::showMentionPopup, this, &TabRoom::actShowMentionPopup);
connect(chatView, &ChatView::messageClickedSignal, this, &TabRoom::focusTab);
connect(chatView, &ChatView::openMessageDialog, this, &TabRoom::openMessageDialog);
connect(chatView, &ChatView::showCardInfoPopup, this, &TabRoom::showCardInfoPopup);
connect(chatView, &ChatView::deleteCardInfoPopup, this, &TabRoom::deleteCardInfoPopup);
connect(chatView, &ChatView::addMentionTag, this, &TabRoom::addMentionTag);
connect(&SettingsCache::instance(), &SettingsCache::chatMentionCompleterChanged, this,
&TabRoom::actCompleterChanged);
sayLabel = new QLabel;
sayEdit = new LineEditCompleter;
sayEdit->setMaxLength(MAX_TEXT_LENGTH);
sayLabel->setBuddy(sayEdit);
connect(sayEdit, &LineEditCompleter::returnPressed, this, &TabRoom::sendMessage);
QMenu *chatSettingsMenu = new QMenu(this);
aClearChat = chatSettingsMenu->addAction(QString());
connect(aClearChat, &QAction::triggered, this, &TabRoom::actClearChat);
chatSettingsMenu->addSeparator();
aOpenChatSettings = chatSettingsMenu->addAction(QString());
connect(aOpenChatSettings, &QAction::triggered, this, &TabRoom::actOpenChatSettings);
QToolButton *chatSettingsButton = new QToolButton;
chatSettingsButton->setIcon(QPixmap("theme:icons/settings"));
chatSettingsButton->setMenu(chatSettingsMenu);
chatSettingsButton->setPopupMode(QToolButton::InstantPopup);
QHBoxLayout *sayHbox = new QHBoxLayout;
sayHbox->addWidget(sayLabel);
sayHbox->addWidget(sayEdit);
sayHbox->addWidget(chatSettingsButton);
QVBoxLayout *chatVbox = new QVBoxLayout;
chatVbox->addWidget(chatView);
chatVbox->addLayout(sayHbox);
chatGroupBox = new QGroupBox;
chatGroupBox->setLayout(chatVbox);
QSplitter *splitter = new QSplitter(Qt::Vertical);
splitter->addWidget(gameSelector);
splitter->addWidget(chatGroupBox);
QHBoxLayout *hbox = new QHBoxLayout;
hbox->addWidget(splitter, 3);
hbox->addWidget(userList, 1);
aLeaveRoom = new QAction(this);
connect(aLeaveRoom, &QAction::triggered, this, &TabRoom::closeRequest);
roomMenu = new QMenu(this);
roomMenu->addAction(aLeaveRoom);
addTabMenu(roomMenu);
const int userListSize = info.user_list_size();
for (int i = 0; i < userListSize; ++i) {
userList->processUserInfo(info.user_list(i), true);
autocompleteUserList.append("@" + QString::fromStdString(info.user_list(i).name()));
}
userList->sortItems();
const int gameListSize = info.game_list_size();
for (int i = 0; i < gameListSize; ++i)
gameSelector->processGameInfo(info.game_list(i));
completer = new QCompleter(autocompleteUserList, sayEdit);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setMaxVisibleItems(5);
completer->setFilterMode(Qt::MatchStartsWith);
sayEdit->setCompleter(completer);
actCompleterChanged();
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&TabRoom::refreshShortcuts);
refreshShortcuts();
retranslateUi();
QWidget *mainWidget = new QWidget(this);
mainWidget->setLayout(hbox);
setCentralWidget(mainWidget);
}
void TabRoom::retranslateUi()
{
gameSelector->retranslateUi();
chatView->retranslateUi();
userList->retranslateUi();
sayLabel->setText(tr("&Say:"));
chatGroupBox->setTitle(tr("Chat"));
roomMenu->setTitle(tr("&Room"));
aLeaveRoom->setText(tr("&Leave room"));
aClearChat->setText(tr("&Clear chat"));
aOpenChatSettings->setText(tr("Chat Settings..."));
}
void TabRoom::focusTab()
{
activateWindow();
tabSupervisor->setCurrentIndex(tabSupervisor->indexOf(this));
emit maximizeClient();
}
void TabRoom::actShowMentionPopup(const QString &sender)
{
this->actShowPopup(sender + tr(" mentioned you."));
}
void TabRoom::actShowPopup(const QString &message)
{
if (trayIcon && (tabSupervisor->currentIndex() != tabSupervisor->indexOf(this) ||
QApplication::activeWindow() == nullptr || QApplication::focusWidget() == nullptr)) {
disconnect(trayIcon, &QSystemTrayIcon::messageClicked, nullptr, nullptr);
trayIcon->showMessage(message, tr("Click to view"));
connect(trayIcon, &QSystemTrayIcon::messageClicked, chatView, &ChatView::messageClickedSignal);
}
}
void TabRoom::closeEvent(QCloseEvent *event)
{
sendRoomCommand(prepareRoomCommand(Command_LeaveRoom()));
emit roomClosing(this);
event->accept();
}
void TabRoom::tabActivated()
{
if (!sayEdit->hasFocus())
sayEdit->setFocus();
}
QString TabRoom::sanitizeHtml(QString dirty) const
{
return dirty.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
}
void TabRoom::sendMessage()
{
if (sayEdit->text().isEmpty()) {
return;
} else if (completer->popup()->isVisible()) {
completer->popup()->hide();
return;
} else {
Command_RoomSay cmd;
cmd.set_message(sayEdit->text().toStdString());
PendingCommand *pend = prepareRoomCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabRoom::sayFinished);
sendRoomCommand(pend);
sayEdit->clear();
}
}
void TabRoom::sayFinished(const Response &response)
{
if (response.response_code() == Response::RespChatFlood)
chatView->appendMessage(tr("You are flooding the chat. Please wait a couple of seconds."));
}
void TabRoom::actClearChat()
{
chatView->clearChat();
}
void TabRoom::actOpenChatSettings()
{
DlgSettings settings(this);
settings.setTab(4);
settings.exec();
}
void TabRoom::actCompleterChanged()
{
SettingsCache::instance().getChatMentionCompleter() ? completer->setCompletionRole(2)
: completer->setCompletionRole(1);
}
void TabRoom::processRoomEvent(const RoomEvent &event)
{
switch (static_cast<RoomEvent::RoomEventType>(getPbExtension(event))) {
case RoomEvent::LIST_GAMES:
processListGamesEvent(event.GetExtension(Event_ListGames::ext));
break;
case RoomEvent::JOIN_ROOM:
processJoinRoomEvent(event.GetExtension(Event_JoinRoom::ext));
break;
case RoomEvent::LEAVE_ROOM:
processLeaveRoomEvent(event.GetExtension(Event_LeaveRoom::ext));
break;
case RoomEvent::ROOM_SAY:
processRoomSayEvent(event.GetExtension(Event_RoomSay::ext));
break;
case RoomEvent::REMOVE_MESSAGES:
processRemoveMessagesEvent(event.GetExtension(Event_RemoveMessages::ext));
break;
default:;
}
}
void TabRoom::processListGamesEvent(const Event_ListGames &event)
{
const int gameListSize = event.game_list_size();
for (int i = 0; i < gameListSize; ++i)
gameSelector->processGameInfo(event.game_list(i));
}
void TabRoom::processJoinRoomEvent(const Event_JoinRoom &event)
{
userList->processUserInfo(event.user_info(), true);
userList->sortItems();
if (!autocompleteUserList.contains("@" + QString::fromStdString(event.user_info().name()))) {
autocompleteUserList << "@" + QString::fromStdString(event.user_info().name());
sayEdit->setCompletionList(autocompleteUserList);
}
}
void TabRoom::processLeaveRoomEvent(const Event_LeaveRoom &event)
{
userList->deleteUser(QString::fromStdString(event.name()));
autocompleteUserList.removeOne("@" + QString::fromStdString(event.name()));
sayEdit->setCompletionList(autocompleteUserList);
}
void TabRoom::processRoomSayEvent(const Event_RoomSay &event)
{
QString senderName = QString::fromStdString(event.name());
QString message = QString::fromStdString(event.message());
if (userListProxy->isUserIgnored(senderName))
return;
UserListTWI *twi = userList->getUsers().value(senderName);
ServerInfo_User userInfo = {};
if (twi) {
userInfo = twi->getUserInfo();
if (SettingsCache::instance().getIgnoreUnregisteredUsers() &&
!UserLevelFlags(userInfo.user_level()).testFlag(ServerInfo_User::IsRegistered))
return;
}
if (event.message_type() == Event_RoomSay::ChatHistory && !SettingsCache::instance().getRoomHistory())
return;
if (event.message_type() == Event_RoomSay::ChatHistory)
message =
"[" +
QString(QDateTime::fromMSecsSinceEpoch(event.time_of()).toLocalTime().toString("d MMM yyyy HH:mm:ss")) +
"] " + message;
chatView->appendMessage(message, event.message_type(), userInfo, true);
emit userEvent(false);
}
void TabRoom::processRemoveMessagesEvent(const Event_RemoveMessages &event)
{
QString userName = QString::fromStdString(event.name());
int amount = event.amount();
chatView->redactMessages(userName, amount);
}
void TabRoom::refreshShortcuts()
{
aClearChat->setShortcuts(SettingsCache::instance().shortcuts().getShortcut("tab_room/aClearChat"));
}
void TabRoom::addMentionTag(QString mentionTag)
{
sayEdit->insert(mentionTag + " ");
sayEdit->setFocus();
}
PendingCommand *TabRoom::prepareRoomCommand(const ::google::protobuf::Message &cmd)
{
return client->prepareRoomCommand(cmd, roomId);
}
void TabRoom::sendRoomCommand(PendingCommand *pend)
{
client->sendCommand(pend);
}

View file

@ -0,0 +1,134 @@
/**
* @file tab_room.h
* @ingroup RoomTabs
* @ingroup Lobby
* @brief TODO: Document this.
*/
#ifndef TAB_ROOM_H
#define TAB_ROOM_H
#include "../interface/widgets/utility/line_edit_completer.h"
#include "tab.h"
#include <QFocusEvent>
#include <QGroupBox>
#include <QKeyEvent>
#include <QMap>
class UserListProxy;
class UserListManager;
namespace google
{
namespace protobuf
{
class Message;
}
} // namespace google
class AbstractClient;
class UserListWidget;
class QLabel;
class ChatView;
class QPushButton;
class QTextTable;
class QCompleter;
class RoomEvent;
class ServerInfo_Room;
class ServerInfo_Game;
class Event_ListGames;
class Event_JoinRoom;
class Event_LeaveRoom;
class Event_RoomSay;
class Event_RemoveMessages;
class GameSelector;
class Response;
class PendingCommand;
class ServerInfo_User;
class LineEditCompleter;
class TabRoom : public Tab
{
Q_OBJECT
private:
AbstractClient *client;
int roomId;
QString roomName;
ServerInfo_User *ownUser;
QMap<int, QString> gameTypes;
GameSelector *gameSelector;
UserListWidget *userList;
const UserListProxy *userListProxy;
ChatView *chatView;
QLabel *sayLabel;
LineEditCompleter *sayEdit;
QGroupBox *chatGroupBox;
QMenu *roomMenu;
QAction *aLeaveRoom;
QAction *aOpenChatSettings;
QAction *aClearChat;
QString sanitizeHtml(QString dirty) const;
QStringList autocompleteUserList;
QCompleter *completer;
signals:
void roomClosing(TabRoom *tab);
void openMessageDialog(const QString &userName, bool focus);
void maximizeClient();
void notIdle();
private slots:
void sendMessage();
void sayFinished(const Response &response);
void actClearChat();
void actOpenChatSettings();
void addMentionTag(QString mentionTag);
void focusTab();
void actShowMentionPopup(const QString &sender);
void actShowPopup(const QString &message);
void actCompleterChanged();
void processListGamesEvent(const Event_ListGames &event);
void processJoinRoomEvent(const Event_JoinRoom &event);
void processLeaveRoomEvent(const Event_LeaveRoom &event);
void processRoomSayEvent(const Event_RoomSay &event);
void processRemoveMessagesEvent(const Event_RemoveMessages &event);
void refreshShortcuts();
protected slots:
void closeEvent(QCloseEvent *event) override;
public:
TabRoom(TabSupervisor *_tabSupervisor,
AbstractClient *_client,
ServerInfo_User *_ownUser,
const ServerInfo_Room &info);
void retranslateUi() override;
void tabActivated() override;
void processRoomEvent(const RoomEvent &event);
int getRoomId() const
{
return roomId;
}
const QMap<int, QString> &getGameTypes() const
{
return gameTypes;
}
QString getChannelName() const
{
return roomName;
}
QString getTabText() const override
{
return roomName;
}
const ServerInfo_User *getUserInfo() const
{
return ownUser;
}
PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd);
void sendRoomCommand(PendingCommand *pend);
};
#endif

View file

@ -0,0 +1,227 @@
#include "tab_server.h"
#include "../interface/widgets/server/user/user_list_widget.h"
#include "tab_supervisor.h"
#include <QCheckBox>
#include <QDebug>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QInputDialog>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QTextEdit>
#include <QTreeView>
#include <QVBoxLayout>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pb/event_list_rooms.pb.h>
#include <libcockatrice/protocol/pb/event_server_message.pb.h>
#include <libcockatrice/protocol/pb/response_join_room.pb.h>
#include <libcockatrice/protocol/pb/session_commands.pb.h>
#include <libcockatrice/protocol/pending_command.h>
RoomSelector::RoomSelector(AbstractClient *_client, QWidget *parent) : QGroupBox(parent), client(_client)
{
roomList = new QTreeWidget;
roomList->setRootIsDecorated(false);
roomList->setColumnCount(5);
roomList->header()->setStretchLastSection(false);
roomList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
roomList->header()->setSectionResizeMode(1, QHeaderView::Stretch);
roomList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
roomList->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
joinButton = new QPushButton;
connect(joinButton, &QPushButton::clicked, this, &RoomSelector::joinClicked);
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addStretch();
buttonLayout->addWidget(joinButton);
QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(roomList);
vbox->addLayout(buttonLayout);
retranslateUi();
setLayout(vbox);
connect(client, &AbstractClient::listRoomsEventReceived, this, &RoomSelector::processListRoomsEvent);
connect(roomList, &QTreeWidget::activated, this, &RoomSelector::joinClicked);
client->sendCommand(client->prepareSessionCommand(Command_ListRooms()));
}
void RoomSelector::retranslateUi()
{
setTitle(tr("Rooms"));
joinButton->setText(tr("Joi&n"));
QTreeWidgetItem *header = roomList->headerItem();
header->setText(0, tr("Room"));
header->setText(1, tr("Description"));
header->setText(2, tr("Permissions"));
header->setText(3, tr("Players"));
header->setText(4, tr("Games"));
header->setTextAlignment(2, Qt::AlignRight);
header->setTextAlignment(3, Qt::AlignRight);
header->setTextAlignment(4, Qt::AlignRight);
}
void RoomSelector::processListRoomsEvent(const Event_ListRooms &event)
{
const int roomListSize = event.room_list_size();
for (int i = 0; i < roomListSize; ++i) {
const ServerInfo_Room &room = event.room_list(i);
for (int j = 0; j < roomList->topLevelItemCount(); ++j) {
QTreeWidgetItem *twi = roomList->topLevelItem(j);
if (twi->data(0, Qt::UserRole).toInt() == room.room_id()) {
if (room.has_name())
twi->setData(0, Qt::DisplayRole, QString::fromStdString(room.name()));
if (room.has_description())
twi->setData(1, Qt::DisplayRole, QString::fromStdString(room.description()));
if (room.has_permissionlevel())
twi->setData(2, Qt::DisplayRole, getRoomPermissionDisplay(room));
if (room.has_player_count())
twi->setData(3, Qt::DisplayRole, room.player_count());
if (room.has_game_count())
twi->setData(4, Qt::DisplayRole, room.game_count());
return;
}
}
QTreeWidgetItem *twi = new QTreeWidgetItem;
twi->setData(0, Qt::UserRole, room.room_id());
if (room.has_name())
twi->setData(0, Qt::DisplayRole, QString::fromStdString(room.name()));
if (room.has_description())
twi->setData(1, Qt::DisplayRole, QString::fromStdString(room.description()));
if (room.has_permissionlevel())
twi->setData(2, Qt::DisplayRole, getRoomPermissionDisplay(room));
twi->setData(3, Qt::DisplayRole, room.player_count());
twi->setData(4, Qt::DisplayRole, room.game_count());
twi->setTextAlignment(2, Qt::AlignRight);
twi->setTextAlignment(3, Qt::AlignRight);
twi->setTextAlignment(4, Qt::AlignRight);
roomList->addTopLevelItem(twi);
if (room.has_auto_join())
if (room.auto_join())
emit joinRoomRequest(room.room_id(), false);
}
}
QString RoomSelector::getRoomPermissionDisplay(const ServerInfo_Room &room)
{
/*
* A server room can have a permission level and a privilege level. How ever we want to display only the necessary
* information on the server tab needed to inform users of required permissions to enter a room. If the room has a
* privilege level the server tab will display the privilege level in the "permissions" column in the row however if
* the room contains a permissions level for the room the permissions level defined for the room will be displayed.
*/
QString roomPermissionDisplay = QString::fromStdString(room.privilegelevel()).toLower();
if (QString::fromStdString(room.permissionlevel()).toLower() != "none")
roomPermissionDisplay = QString::fromStdString(room.permissionlevel()).toLower();
if (roomPermissionDisplay == "") // catch all for misconfigured .ini room definitions
roomPermissionDisplay = "none";
return roomPermissionDisplay;
}
void RoomSelector::joinClicked()
{
QTreeWidgetItem *twi = roomList->currentItem();
if (!twi)
return;
int id = twi->data(0, Qt::UserRole).toInt();
emit joinRoomRequest(id, true);
}
TabServer::TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
{
roomSelector = new RoomSelector(client);
serverInfoBox = new QTextBrowser;
serverInfoBox->setOpenExternalLinks(true);
connect(roomSelector, &RoomSelector::joinRoomRequest, this, &TabServer::joinRoom);
connect(client, &AbstractClient::serverMessageEventReceived, this, &TabServer::processServerMessageEvent);
QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(roomSelector);
vbox->addWidget(serverInfoBox);
retranslateUi();
QWidget *mainWidget = new QWidget(this);
mainWidget->setLayout(vbox);
setCentralWidget(mainWidget);
}
void TabServer::retranslateUi()
{
roomSelector->retranslateUi();
}
void TabServer::processServerMessageEvent(const Event_ServerMessage &event)
{
serverInfoBox->setHtml(QString::fromStdString(event.message()));
if (shouldEmitUpdate) {
// prevent the initial server message from taking attention from ping icon
emit userEvent();
} else {
shouldEmitUpdate = true;
}
}
void TabServer::joinRoom(int id, bool setCurrent)
{
TabRoom *room = tabSupervisor->getRoomTabs().value(id);
if (!room) {
Command_JoinRoom cmd;
cmd.set_room_id(id);
PendingCommand *pend = client->prepareSessionCommand(cmd);
pend->setExtraData(setCurrent);
connect(pend, &PendingCommand::finished, this, &TabServer::joinRoomFinished);
client->sendCommand(pend);
return;
}
if (setCurrent)
tabSupervisor->setCurrentWidget((QWidget *)room);
}
void TabServer::joinRoomFinished(const Response &r,
const CommandContainer & /*commandContainer*/,
const QVariant &extraData)
{
switch (r.response_code()) {
case Response::RespOk:
break;
case Response::RespNameNotFound:
QMessageBox::critical(this, tr("Error"),
tr("Failed to join the server room: it doesn't exist on the server."));
return;
case Response::RespContextError:
QMessageBox::critical(
this, tr("Error"),
tr("The server thinks you are in the server room but your client is unable to display it. "
"Try restarting your client."));
return;
case Response::RespUserLevelTooLow:
QMessageBox::critical(this, tr("Error"),
tr("You do not have the required permission to join this server room."));
return;
default:
QMessageBox::critical(
this, tr("Error"),
tr("Failed to join the server room due to an unknown error: %1.").arg(r.response_code()));
return;
}
const Response_JoinRoom &resp = r.GetExtension(Response_JoinRoom::ext);
emit roomJoined(resp.room_info(), extraData.toBool());
}

View file

@ -0,0 +1,72 @@
/**
* @file tab_server.h
* @ingroup ServerTabs
* @brief TODO: Document this.
*/
#ifndef TAB_SERVER_H
#define TAB_SERVER_H
#include "tab.h"
#include <QGroupBox>
#include <QTextBrowser>
#include <QTreeWidget>
class AbstractClient;
class QTextEdit;
class QLabel;
class UserListWidget;
class QPushButton;
class Event_ListRooms;
class Event_ServerMessage;
class Response;
class ServerInfo_Room;
class CommandContainer;
class RoomSelector : public QGroupBox
{
Q_OBJECT
private:
QTreeWidget *roomList;
QPushButton *joinButton;
AbstractClient *client;
QString getRoomPermissionDisplay(const ServerInfo_Room &room);
private slots:
void processListRoomsEvent(const Event_ListRooms &event);
void joinClicked();
signals:
void joinRoomRequest(int, bool setCurrent);
public:
explicit RoomSelector(AbstractClient *_client, QWidget *parent = nullptr);
void retranslateUi();
};
class TabServer : public Tab
{
Q_OBJECT
signals:
void roomJoined(const ServerInfo_Room &info, bool setCurrent);
private slots:
void processServerMessageEvent(const Event_ServerMessage &event);
void joinRoom(int id, bool setCurrent);
void joinRoomFinished(const Response &resp, const CommandContainer &commandContainer, const QVariant &extraData);
private:
AbstractClient *client;
RoomSelector *roomSelector;
QTextBrowser *serverInfoBox;
bool shouldEmitUpdate = false;
public:
TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client);
void retranslateUi() override;
QString getTabText() const override
{
return tr("Server");
}
};
#endif

File diff suppressed because it is too large Load diff

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