Merge branch 'master' into remote-decks-multi

This commit is contained in:
RickyRister 2024-12-23 17:44:01 -08:00 committed by GitHub
commit 9c914e6217
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1669 additions and 1158 deletions

View file

@ -41,6 +41,7 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c
localDirView->setColumnHidden(1, true); localDirView->setColumnHidden(1, true);
localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0)); localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0));
localDirView->setSortingEnabled(true); localDirView->setSortingEnabled(true);
localDirView->setSelectionMode(QAbstractItemView::ExtendedSelection);
localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
localDirView->header()->setSortIndicator(0, Qt::AscendingOrder); localDirView->header()->setSortIndicator(0, Qt::AscendingOrder);
@ -149,41 +150,50 @@ QString TabDeckStorage::getTargetPath() const
void TabDeckStorage::actOpenLocalDeck() void TabDeckStorage::actOpenLocalDeck()
{ {
QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
if (localDirModel->isDir(curLeft)) for (const auto &curLeft : curLefts) {
return; if (localDirModel->isDir(curLeft))
QString filePath = localDirModel->filePath(curLeft); return;
QString filePath = localDirModel->filePath(curLeft);
DeckLoader deckLoader; DeckLoader deckLoader;
if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat)) if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat))
return; return;
emit openDeckEditor(&deckLoader); emit openDeckEditor(&deckLoader);
}
} }
void TabDeckStorage::actUpload() void TabDeckStorage::actUpload()
{ {
QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
if (localDirModel->isDir(curLeft)) if (curLefts.isEmpty()) {
return; return;
}
QString targetPath = getTargetPath(); QString targetPath = getTargetPath();
if (targetPath.length() > MAX_NAME_LENGTH) { if (targetPath.length() > MAX_NAME_LENGTH) {
qCritical() << "target path to upload to is too long" << targetPath; qCritical() << "target path to upload to is too long" << targetPath;
return; return;
} }
QString filePath = localDirModel->filePath(curLeft); for (const auto &curLeft : curLefts) {
if (localDirModel->isDir(curLeft)) {
continue;
}
QString filePath = localDirModel->filePath(curLeft);
uploadDeck(filePath, targetPath);
}
}
void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPath)
{
QFile deckFile(filePath); QFile deckFile(filePath);
QFileInfo deckFileInfo(deckFile); QFileInfo deckFileInfo(deckFile);
QString deckString;
DeckLoader deck; DeckLoader deck;
bool error = !deck.loadFromFile(filePath, DeckLoader::CockatriceFormat); if (!deck.loadFromFile(filePath, DeckLoader::CockatriceFormat)) {
if (!error) {
deckString = deck.writeToString_Native();
error = deckString.length() > MAX_FILE_LENGTH;
}
if (error) {
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file")); QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
return; return;
} }
@ -202,6 +212,12 @@ void TabDeckStorage::actUpload()
deck.setName(deck.getName().left(MAX_NAME_LENGTH)); 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; Command_DeckUpload cmd;
cmd.set_path(targetPath.toStdString()); cmd.set_path(targetPath.toStdString());
cmd.set_deck_list(deckString.toStdString()); cmd.set_deck_list(deckString.toStdString());
@ -228,16 +244,22 @@ void TabDeckStorage::uploadFinished(const Response &r, const CommandContainer &c
void TabDeckStorage::actDeleteLocalDeck() void TabDeckStorage::actDeleteLocalDeck()
{ {
QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); const QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
if (localDirModel->isDir(curLeft))
return;
if (QMessageBox::warning(this, tr("Delete local file"), auto isDir = [&](const auto &curLeft) { return localDirModel->isDir(curLeft); };
tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)), if (curLefts.isEmpty() || std::all_of(curLefts.begin(), curLefts.end(), isDir)) {
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) QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return; return;
localDirModel->remove(curLeft); for (const auto &curLeft : curLefts) {
if (!localDirModel->isDir(curLeft)) {
localDirModel->remove(curLeft);
}
}
} }
void TabDeckStorage::actOpenRemoteDeck() void TabDeckStorage::actOpenRemoteDeck()

View file

@ -29,6 +29,7 @@ private:
QAction *aOpenLocalDeck, *aUpload, *aDeleteLocalDeck, *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck; QAction *aOpenLocalDeck, *aUpload, *aDeleteLocalDeck, *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck;
QString getTargetPath() const; QString getTargetPath() const;
void uploadDeck(const QString &filePath, const QString &targetPath);
void deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *node); void deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *node);
private slots: private slots:

View file

@ -36,6 +36,7 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) :
localDirView->setColumnHidden(1, true); localDirView->setColumnHidden(1, true);
localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0)); localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0));
localDirView->setSortingEnabled(true); localDirView->setSortingEnabled(true);
localDirView->setSelectionMode(QAbstractItemView::ExtendedSelection);
localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
localDirView->header()->setSortIndicator(0, Qt::AscendingOrder); localDirView->header()->setSortIndicator(0, Qt::AscendingOrder);
@ -126,50 +127,62 @@ void TabReplays::retranslateUi()
void TabReplays::actOpenLocalReplay() void TabReplays::actOpenLocalReplay()
{ {
QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
if (localDirModel->isDir(curLeft)) for (const auto &curLeft : curLefts) {
return; if (localDirModel->isDir(curLeft))
QString filePath = localDirModel->filePath(curLeft); continue;
QString filePath = localDirModel->filePath(curLeft);
QFile f(filePath); QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) if (!f.open(QIODevice::ReadOnly))
return; continue;
QByteArray _data = f.readAll(); QByteArray _data = f.readAll();
f.close(); f.close();
GameReplay *replay = new GameReplay; GameReplay *replay = new GameReplay;
replay->ParseFromArray(_data.data(), _data.size()); replay->ParseFromArray(_data.data(), _data.size());
emit openReplay(replay); emit openReplay(replay);
}
} }
void TabReplays::actDeleteLocalReplay() void TabReplays::actDeleteLocalReplay()
{ {
QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
if (!curLeft.isValid())
return;
if (QMessageBox::warning(this, tr("Delete local file"), if (curLefts.isEmpty()) {
tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return; return;
}
localDirModel->remove(curLeft); 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::actOpenRemoteReplay() void TabReplays::actOpenRemoteReplay()
{ {
ServerInfo_Replay const *curRight = serverDirView->getCurrentReplay(); auto const curRights = serverDirView->getSelectedReplays();
if (!curRight)
return;
Command_ReplayDownload cmd; for (const auto curRight : curRights) {
cmd.set_replay_id(curRight->replay_id()); if (!curRight) {
continue;
}
PendingCommand *pend = client->prepareSessionCommand(cmd); Command_ReplayDownload cmd;
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, cmd.set_replay_id(curRight->replay_id());
SLOT(openRemoteReplayFinished(const Response &)));
client->sendCommand(pend); PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(openRemoteReplayFinished(const Response &)));
client->sendCommand(pend);
}
} }
void TabReplays::openRemoteReplayFinished(const Response &r) void TabReplays::openRemoteReplayFinished(const Response &r)
@ -186,34 +199,37 @@ void TabReplays::openRemoteReplayFinished(const Response &r)
void TabReplays::actDownload() void TabReplays::actDownload()
{ {
QString filePath; QString dirPath;
QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
if (!curLeft.isValid()) if (!curLeft.isValid())
filePath = localDirModel->rootPath(); dirPath = localDirModel->rootPath();
else { else {
while (!localDirModel->isDir(curLeft)) while (!localDirModel->isDir(curLeft))
curLeft = curLeft.parent(); curLeft = curLeft.parent();
filePath = localDirModel->filePath(curLeft); dirPath = localDirModel->filePath(curLeft);
} }
ServerInfo_Replay const *curRight = serverDirView->getCurrentReplay(); const auto curRights = serverDirView->getSelectedReplays();
if (!curRight) { const auto isNull = [](const auto *replay) { return !replay; };
if (std::any_of(curRights.begin(), curRights.end(), isNull)) {
QMessageBox::information(this, tr("Downloading Replays"), QMessageBox::information(this, tr("Downloading Replays"),
tr("Folder download is not yet supported. Please download replays individually.")); tr("Folder download is not yet supported. Please download replays individually."));
return; return;
} }
filePath += QString("/replay_%1.cor").arg(curRight->replay_id()); for (const auto curRight : curRights) {
const QString filePath = dirPath + QString("/replay_%1.cor").arg(curRight->replay_id());
Command_ReplayDownload cmd; Command_ReplayDownload cmd;
cmd.set_replay_id(curRight->replay_id()); cmd.set_replay_id(curRight->replay_id());
PendingCommand *pend = client->prepareSessionCommand(cmd); PendingCommand *pend = client->prepareSessionCommand(cmd);
pend->setExtraData(filePath); pend->setExtraData(filePath);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(downloadFinished(Response, CommandContainer, QVariant))); SLOT(downloadFinished(Response, CommandContainer, QVariant)));
client->sendCommand(pend); client->sendCommand(pend);
}
} }
void TabReplays::downloadFinished(const Response &r, void TabReplays::downloadFinished(const Response &r,
@ -235,18 +251,22 @@ void TabReplays::downloadFinished(const Response &r,
void TabReplays::actKeepRemoteReplay() void TabReplays::actKeepRemoteReplay()
{ {
ServerInfo_ReplayMatch const *curRight = serverDirView->getCurrentReplayMatch(); const auto curRights = serverDirView->getSelectedReplayMatches();
if (!curRight)
if (curRights.isEmpty()) {
return; return;
}
Command_ReplayModifyMatch cmd; for (const auto curRight : curRights) {
cmd.set_game_id(curRight->game_id()); Command_ReplayModifyMatch cmd;
cmd.set_do_not_hide(!curRight->do_not_hide()); cmd.set_game_id(curRight->game_id());
cmd.set_do_not_hide(!curRight->do_not_hide());
PendingCommand *pend = client->prepareSessionCommand(cmd); PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(keepRemoteReplayFinished(Response, CommandContainer))); SLOT(keepRemoteReplayFinished(Response, CommandContainer)));
client->sendCommand(pend); client->sendCommand(pend);
}
} }
void TabReplays::keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer) void TabReplays::keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer)
@ -265,21 +285,27 @@ void TabReplays::keepRemoteReplayFinished(const Response &r, const CommandContai
void TabReplays::actDeleteRemoteReplay() void TabReplays::actDeleteRemoteReplay()
{ {
ServerInfo_ReplayMatch const *curRight = serverDirView->getCurrentReplayMatch(); const auto curRights = serverDirView->getSelectedReplayMatches();
if (!curRight)
if (curRights.isEmpty()) {
return; return;
}
if (QMessageBox::warning(this, tr("Delete remote replay"), if (QMessageBox::warning(this, tr("Delete remote replay"),
tr("Are you sure you want to delete the replay of game %1?").arg(curRight->game_id()), tr("Are you sure you want to delete the selected replays?"),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
return; return;
}
Command_ReplayDeleteMatch cmd; for (const auto curRight : curRights) {
cmd.set_game_id(curRight->game_id()); Command_ReplayDeleteMatch cmd;
cmd.set_game_id(curRight->game_id());
PendingCommand *pend = client->prepareSessionCommand(cmd); PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(deleteRemoteReplayFinished(Response, CommandContainer))); SLOT(deleteRemoteReplayFinished(Response, CommandContainer)));
client->sendCommand(pend); client->sendCommand(pend);
}
} }
void TabReplays::deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer) void TabReplays::deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer)

View file

@ -306,14 +306,41 @@ RemoteReplayList_TreeWidget::RemoteReplayList_TreeWidget(AbstractClient *_client
setSortingEnabled(true); setSortingEnabled(true);
proxyModel->sort(0, Qt::AscendingOrder); proxyModel->sort(0, Qt::AscendingOrder);
header()->setSortIndicator(0, Qt::AscendingOrder); header()->setSortIndicator(0, Qt::AscendingOrder);
setSelectionMode(QAbstractItemView::ExtendedSelection);
} }
ServerInfo_Replay const *RemoteReplayList_TreeWidget::getCurrentReplay() const /**
* Gets all currently selected replays.
* Any selection that isn't a replay file (e.g. a folder) will appear as a nullptr in the list.
* Make sure to check the list for nullptr before using it.
*
* @return A List of pointers to the selected replays, as well as nullptr for any selection that isn't a replay.
*/
QList<ServerInfo_Replay const *> RemoteReplayList_TreeWidget::getSelectedReplays() const
{ {
return treeModel->getReplay(proxyModel->mapToSource(selectionModel()->currentIndex())); const auto selection = selectionModel()->selectedRows();
auto replays = QList<ServerInfo_Replay const *>();
for (const auto &row : selection) {
replays << treeModel->getReplay(proxyModel->mapToSource(row));
}
return replays;
} }
ServerInfo_ReplayMatch const *RemoteReplayList_TreeWidget::getCurrentReplayMatch() const /**
* Gets all currently selected replayMatches.
*
* @return A Set of pointers to the selected replayMatches.
*/
QSet<ServerInfo_ReplayMatch const *> RemoteReplayList_TreeWidget::getSelectedReplayMatches() const
{ {
return treeModel->getReplayMatch(proxyModel->mapToSource(selectionModel()->currentIndex())); const auto selection = selectionModel()->selectedRows();
auto replayMatches = QSet<ServerInfo_ReplayMatch const *>();
for (const auto &row : selection) {
if (const auto replayMatch = treeModel->getReplayMatch(proxyModel->mapToSource(row))) {
replayMatches << replayMatch;
}
}
return replayMatches;
} }

View file

@ -111,8 +111,8 @@ private:
public: public:
RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr);
ServerInfo_Replay const *getCurrentReplay() const; QList<ServerInfo_Replay const *> getSelectedReplays() const;
ServerInfo_ReplayMatch const *getCurrentReplayMatch() const; QSet<ServerInfo_ReplayMatch const *> getSelectedReplayMatches() const;
void refreshTree(); void refreshTree();
void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo) void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo)
{ {

View file

@ -4,7 +4,22 @@
#include "shortcuts_settings.h" #include "shortcuts_settings.h"
#include <QHeaderView> #include <QHeaderView>
#include <QSortFilterProxyModel>
ShortcutFilterProxyModel::ShortcutFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
{
}
/**
* @return True if this row or its parent matches the search string
*/
bool ShortcutFilterProxyModel::filterAcceptsRow(const int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex nameIndex = sourceModel()->index(sourceRow, filterKeyColumn(), sourceParent);
QModelIndex parentIndex = sourceModel()->index(sourceParent.row(), filterKeyColumn(), sourceParent.parent());
return sourceModel()->data(nameIndex).toString().contains(filterRegularExpression()) ||
sourceModel()->data(parentIndex).toString().contains(filterRegularExpression());
}
ShortcutTreeView::ShortcutTreeView(QWidget *parent) : QTreeView(parent) ShortcutTreeView::ShortcutTreeView(QWidget *parent) : QTreeView(parent)
{ {
@ -14,7 +29,7 @@ ShortcutTreeView::ShortcutTreeView(QWidget *parent) : QTreeView(parent)
populateShortcutsModel(); populateShortcutsModel();
// filter proxy // filter proxy
proxyModel = new QSortFilterProxyModel(this); proxyModel = new ShortcutFilterProxyModel(this);
proxyModel->setRecursiveFilteringEnabled(true); proxyModel->setRecursiveFilteringEnabled(true);
proxyModel->setSourceModel(shortcutsModel); proxyModel->setSourceModel(shortcutsModel);
proxyModel->setDynamicSortFilter(true); proxyModel->setDynamicSortFilter(true);

View file

@ -2,10 +2,23 @@
#define SHORTCUT_TREEVIEW_H #define SHORTCUT_TREEVIEW_H
#include <QModelIndex> #include <QModelIndex>
#include <QSortFilterProxyModel>
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QTreeView> #include <QTreeView>
class QSortFilterProxyModel; /**
* Custom implementation of QSortFilterProxyModel that also searches in the parent's string when filtering
*/
class ShortcutFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit ShortcutFilterProxyModel(QObject *parent = nullptr);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
};
class ShortcutTreeView : public QTreeView class ShortcutTreeView : public QTreeView
{ {
Q_OBJECT Q_OBJECT
@ -21,7 +34,7 @@ public slots:
private: private:
QStandardItemModel *shortcutsModel; QStandardItemModel *shortcutsModel;
QSortFilterProxyModel *proxyModel; ShortcutFilterProxyModel *proxyModel;
void populateShortcutsModel(); void populateShortcutsModel();
private slots: private slots:

File diff suppressed because it is too large Load diff