mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-09 01:23:57 -07:00
Merge branch 'master' into tooomm-qt5
This commit is contained in:
commit
9d4cf57c70
593 changed files with 12518 additions and 6581 deletions
|
|
@ -150,9 +150,10 @@ void CardPictureLoader::getPixmap(QPixmap &pixmap, const ExactCard &card, QSize
|
|||
|
||||
void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image)
|
||||
{
|
||||
QPixmap finalPixmap;
|
||||
|
||||
if (image.isNull()) {
|
||||
qCDebug(CardPictureLoaderLog) << "Caching NULL pixmap for" << card.getName();
|
||||
QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap());
|
||||
} else {
|
||||
if (card.getInfo().getUiAttributes().upsideDownArt) {
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0))
|
||||
|
|
@ -160,12 +161,19 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image)
|
|||
#else
|
||||
QImage mirrorImage = image.mirrored(true, true);
|
||||
#endif
|
||||
QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap::fromImage(mirrorImage));
|
||||
finalPixmap = QPixmap::fromImage(mirrorImage);
|
||||
} else {
|
||||
QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap::fromImage(image));
|
||||
finalPixmap = QPixmap::fromImage(image);
|
||||
}
|
||||
}
|
||||
|
||||
QPixmapCache::insert(card.getPixmapCacheKey(), finalPixmap);
|
||||
|
||||
if (SettingsCache::instance().getCardPictureLoaderCacheMethod() ==
|
||||
CardPictureLoaderCacheMethod::CacheMethod::FILESYSTEM_CACHE) {
|
||||
saveCardImageToLocalStorage(card, finalPixmap);
|
||||
}
|
||||
|
||||
// imageLoaded should only be reached if the exactCard isn't already in cache.
|
||||
// (plus there's a deduplication mechanism in CardPictureLoaderWorker)
|
||||
// It should be safe to connect the CardInfo here without worrying about redundant connections.
|
||||
|
|
@ -175,6 +183,88 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image)
|
|||
card.emitPixmapUpdated();
|
||||
}
|
||||
|
||||
void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap)
|
||||
{
|
||||
if (pixmap.isNull() || !card) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString picsRoot = SettingsCache::instance().getPicsPath();
|
||||
CardPictureLoaderLocalSchemes::NamingScheme scheme =
|
||||
SettingsCache::instance().getLocalCardImageStorageNamingScheme();
|
||||
|
||||
QString pattern;
|
||||
|
||||
for (const auto &s : CardPictureLoaderLocalSchemes::exportSchemes()) {
|
||||
if (s.id == scheme) {
|
||||
pattern = s.pattern;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (picsRoot.isEmpty() || pattern.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Base directory: <picsPath>/downloadedPics
|
||||
QDir baseDir(picsRoot);
|
||||
if (!baseDir.exists("downloadedPics")) {
|
||||
baseDir.mkpath("downloadedPics");
|
||||
}
|
||||
baseDir.cd("downloadedPics");
|
||||
|
||||
// Collect card metadata
|
||||
const QString cardName = card.getInfo().getCorrectedName();
|
||||
|
||||
QString setName;
|
||||
QString collectorNumber;
|
||||
QString uuid;
|
||||
|
||||
PrintingInfo printing = card.getPrinting();
|
||||
if (printing.getSet()) {
|
||||
setName = printing.getSet()->getCorrectedShortName();
|
||||
collectorNumber = printing.getProperty("num");
|
||||
uuid = printing.getUuid();
|
||||
}
|
||||
|
||||
// Build path from scheme
|
||||
QString relativePath =
|
||||
CardPictureLoaderLocalSchemes::expandPattern(pattern, cardName, setName, collectorNumber, uuid);
|
||||
|
||||
if (relativePath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// append extension
|
||||
relativePath += ".png";
|
||||
|
||||
// Normalize slashes
|
||||
relativePath = QDir::cleanPath(relativePath);
|
||||
|
||||
QFileInfo outInfo(baseDir.filePath(relativePath));
|
||||
|
||||
// Do not overwrite existing files
|
||||
if (outInfo.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDir outDir = outInfo.dir();
|
||||
|
||||
// Ensure directory exists
|
||||
if (!outDir.exists()) {
|
||||
if (!baseDir.mkpath(outDir.path())) {
|
||||
qCWarning(CardPictureLoaderLog) << "Failed to create directory for downloaded card image:" << outDir.path();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Save image
|
||||
QImage image = pixmap.toImage();
|
||||
if (!image.save(outInfo.absoluteFilePath(), "PNG")) {
|
||||
qCWarning(CardPictureLoaderLog) << "Failed to save card image to" << outInfo.absoluteFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
void CardPictureLoader::clearPixmapCache()
|
||||
{
|
||||
QPixmapCache::clear();
|
||||
|
|
@ -224,8 +314,9 @@ bool CardPictureLoader::hasCustomArt()
|
|||
while (it.hasNext()) {
|
||||
QFileInfo dir(it.nextFileInfo());
|
||||
|
||||
if (it.fileName() == "downloadedPics")
|
||||
if (it.fileName() == "downloadedPics") {
|
||||
continue;
|
||||
}
|
||||
|
||||
QDirIterator subIt(it.filePath(), QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||
if (subIt.hasNext()) {
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ public slots:
|
|||
* @param image Loaded QImage.
|
||||
*/
|
||||
void imageLoaded(const ExactCard &card, const QImage &image);
|
||||
void saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap);
|
||||
|
||||
private slots:
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H
|
||||
#define COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H
|
||||
#include <QCoreApplication>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
namespace CardPictureLoaderCacheMethod
|
||||
{
|
||||
enum class CacheMethod
|
||||
{
|
||||
NETWORK_CACHE,
|
||||
FILESYSTEM_CACHE
|
||||
};
|
||||
|
||||
struct CacheMethodInfo
|
||||
{
|
||||
CacheMethod id;
|
||||
QString displayName;
|
||||
};
|
||||
|
||||
static inline const QList<CacheMethodInfo> methods()
|
||||
{
|
||||
static QList<CacheMethodInfo> all = {
|
||||
{CacheMethod::NETWORK_CACHE, QCoreApplication::translate("CardPictureLoaderCacheMethod", "Network Cache")},
|
||||
{CacheMethod::FILESYSTEM_CACHE, QCoreApplication::translate("CardPictureLoaderCacheMethod", "Filesystem")},
|
||||
};
|
||||
return all;
|
||||
}
|
||||
} // namespace CardPictureLoaderCacheMethod
|
||||
|
||||
#endif // COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#include "card_picture_loader_local.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "card_picture_loader_local_schemes.h"
|
||||
#include "card_picture_to_load.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
|
|
@ -77,26 +78,8 @@ QImage CardPictureLoaderLocal::tryLoadCardImageFromDisk(const QString &setName,
|
|||
imgReader.setDecideFormatFromContent(true);
|
||||
|
||||
// Most-to-least specific, these will fall through in order.
|
||||
QStringList nameVariants;
|
||||
|
||||
// cardName_providerId
|
||||
if (!providerId.isEmpty()) {
|
||||
nameVariants << QString("%1-%2").arg(correctedCardName, providerId)
|
||||
<< QString("%1_%2").arg(correctedCardName, providerId);
|
||||
}
|
||||
// cardName_setName_collectorNumber & setName-collectorNumber-cardName
|
||||
if (!setName.isEmpty() && !collectorNumber.isEmpty()) {
|
||||
nameVariants << QString("%1_%2_%3").arg(correctedCardName, setName, collectorNumber)
|
||||
<< QString("%1-%2-%3").arg(setName, collectorNumber, correctedCardName);
|
||||
}
|
||||
// cardName_setName
|
||||
if (!setName.isEmpty()) {
|
||||
nameVariants << QString("%1_%2").arg(correctedCardName, setName)
|
||||
<< QString("%1-%2").arg(setName, correctedCardName);
|
||||
}
|
||||
|
||||
// cardName
|
||||
nameVariants << correctedCardName;
|
||||
QStringList nameVariants =
|
||||
CardPictureLoaderLocalSchemes::generateImportVariants(correctedCardName, setName, collectorNumber, providerId);
|
||||
|
||||
for (const QString &nameVariant : nameVariants) {
|
||||
if (nameVariant.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
#ifndef COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H
|
||||
#define COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H
|
||||
|
||||
#include <QList>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
namespace CardPictureLoaderLocalSchemes
|
||||
{
|
||||
|
||||
enum class NamingScheme
|
||||
{
|
||||
NameOnly,
|
||||
Name_Set,
|
||||
Name_Set_Collector,
|
||||
Set_Collector_Name,
|
||||
Name_ProviderId,
|
||||
Set_Folder_Name_ProviderId,
|
||||
Set_Folder_Name_Set_Collector
|
||||
};
|
||||
|
||||
struct NamingSchemeInfo
|
||||
{
|
||||
NamingScheme id;
|
||||
QString displayName;
|
||||
QString pattern;
|
||||
};
|
||||
|
||||
inline const QList<NamingSchemeInfo> &importSchemes()
|
||||
{
|
||||
static QList<NamingSchemeInfo> list = {
|
||||
{NamingScheme::Name_ProviderId, "Card Name + Provider ID", "{name}_{providerId}"},
|
||||
{NamingScheme::Name_Set_Collector, "Card Name + Set + Collector", "{name}_{set}_{collector}"},
|
||||
{NamingScheme::Set_Collector_Name, "Set + Collector + Card Name", "{set}_{collector}_{name}"},
|
||||
{NamingScheme::Name_Set, "Card Name + Set", "{name}_{set}"},
|
||||
{NamingScheme::NameOnly, "Card Name", "{name}"},
|
||||
};
|
||||
return list;
|
||||
}
|
||||
|
||||
inline const QList<NamingSchemeInfo> &exportSchemes()
|
||||
{
|
||||
static QList<NamingSchemeInfo> list = {
|
||||
{NamingScheme::Set_Folder_Name_ProviderId, "Set Folder / Name + Provider ID", "{set}/{name}_{providerId}"},
|
||||
{NamingScheme::Set_Folder_Name_Set_Collector, "Set Folder / Name + Set Name + Collector",
|
||||
"{set}/{name}_{set}_{collector}"},
|
||||
{NamingScheme::Name_ProviderId, "Card Name + Provider ID", "{name}_{providerId}"},
|
||||
{NamingScheme::Name_Set_Collector, "Card Name + Set + Collector", "{name}_{set}_{collector}"},
|
||||
{NamingScheme::Set_Collector_Name, "Set + Collector + Card Name", "{set}_{collector}_{name}"},
|
||||
};
|
||||
return list;
|
||||
}
|
||||
|
||||
inline QString expandPattern(const QString &pattern,
|
||||
const QString &name,
|
||||
const QString &set,
|
||||
const QString &collector,
|
||||
const QString &providerId)
|
||||
{
|
||||
QString result = pattern;
|
||||
|
||||
auto replaceIfPresent = [&](const QString &token, const QString &value) -> bool {
|
||||
if (!result.contains(token)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result.replace(token, value);
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!replaceIfPresent("{name}", name)) {
|
||||
return {};
|
||||
}
|
||||
if (!replaceIfPresent("{set}", set)) {
|
||||
return {};
|
||||
}
|
||||
if (!replaceIfPresent("{collector}", collector)) {
|
||||
return {};
|
||||
}
|
||||
if (!replaceIfPresent("{providerId}", providerId)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline QStringList
|
||||
generateImportVariants(const QString &name, const QString &set, const QString &collector, const QString &providerId)
|
||||
{
|
||||
QStringList variants;
|
||||
const QStringList separators = {"_", "-"};
|
||||
|
||||
for (const auto &scheme : importSchemes()) {
|
||||
for (const QString &sep : separators) {
|
||||
|
||||
QString pattern = scheme.pattern;
|
||||
pattern.replace("_", sep);
|
||||
|
||||
QString v = expandPattern(pattern, name, set, collector, providerId);
|
||||
if (!v.isEmpty()) {
|
||||
variants << v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return variants;
|
||||
}
|
||||
|
||||
} // namespace CardPictureLoaderLocalSchemes
|
||||
|
||||
#endif // COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H
|
||||
|
|
@ -26,10 +26,15 @@ CardPictureLoaderWorker::CardPictureLoaderWorker()
|
|||
cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath());
|
||||
cache->setMaximumCacheSize(1024L * 1024L *
|
||||
static_cast<qint64>(SettingsCache::instance().getNetworkCacheSizeInMB()));
|
||||
// Note: the settings is in MB, but QNetworkDiskCache uses bytes
|
||||
connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, this,
|
||||
[this](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast<qint64>(newSizeInMB)); });
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache, [this](int newSizeInMB) {
|
||||
if (cache) {
|
||||
cache->setMaximumCacheSize(1024L * 1024L * static_cast<qint64>(newSizeInMB));
|
||||
}
|
||||
});
|
||||
|
||||
networkManager->setCache(cache);
|
||||
|
||||
// Use a ManualRedirectPolicy since we keep track of redirects in picDownloadFinished
|
||||
// We can't use NoLessSafeRedirectPolicy because it is not applied with AlwaysCache
|
||||
networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
|
||||
|
|
@ -65,14 +70,19 @@ void CardPictureLoaderWorker::queueRequest(const QUrl &url, CardPictureLoaderWor
|
|||
QUrl cachedRedirect = getCachedRedirect(url);
|
||||
if (!cachedRedirect.isEmpty()) {
|
||||
queueRequest(cachedRedirect, worker);
|
||||
} else if (cache->metaData(url).isValid()) {
|
||||
// If we hit a cached url, we get to make the request for free, since it won't contribute towards the rate-limit
|
||||
makeRequest(url, worker);
|
||||
} else {
|
||||
requestLoadQueue.append(qMakePair(url, worker));
|
||||
emit imageRequestQueued(url, worker->cardToDownload.getCard(), worker->cardToDownload.getSetName());
|
||||
processQueuedRequests();
|
||||
return;
|
||||
}
|
||||
if (SettingsCache::instance().getCardPictureLoaderCacheMethod() ==
|
||||
CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE &&
|
||||
cache->metaData(url).isValid()) {
|
||||
// If we hit a cached url, we get to make the request for free, since it won't contribute towards the
|
||||
// rate-limit
|
||||
makeRequest(url, worker);
|
||||
return;
|
||||
}
|
||||
requestLoadQueue.append(qMakePair(url, worker));
|
||||
emit imageRequestQueued(url, worker->cardToDownload.getCard(), worker->cardToDownload.getSetName());
|
||||
processQueuedRequests();
|
||||
}
|
||||
|
||||
QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPictureLoaderWorkerWork *worker)
|
||||
|
|
@ -87,9 +97,12 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture
|
|||
QNetworkRequest req(url);
|
||||
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
req.setRawHeader("Accept", "image/avif,image/webp,image/apng,image/,/*;q=0.8");
|
||||
if (!picDownload) {
|
||||
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
|
||||
}
|
||||
|
||||
bool useNetworkCache = !picDownload && SettingsCache::instance().getCardPictureLoaderCacheMethod() ==
|
||||
CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE;
|
||||
|
||||
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
|
||||
useNetworkCache ? QNetworkRequest::AlwaysCache : QNetworkRequest::AlwaysNetwork);
|
||||
|
||||
QNetworkReply *reply = networkManager->get(req);
|
||||
|
||||
|
|
|
|||
|
|
@ -53,18 +53,19 @@ QList<CardSetPtr> CardPictureToLoad::extractSetsSorted(const ExactCard &card)
|
|||
* PrintingInfo for the set is returned.
|
||||
*
|
||||
* This method only exists to maintain existing behavior.
|
||||
* TODO: check if going through all sets is still necessary after the ExactCard refactor.
|
||||
*
|
||||
* @param card The card to look in
|
||||
* @param setName The set's short name
|
||||
* @return A PrintingInfo, or a default-constructed PrintingInfo if the set name is not in the CardInfo.
|
||||
*/
|
||||
//! \todo Check if going through all sets is still necessary after the ExactCard refactor.
|
||||
static PrintingInfo findPrintingForSet(const ExactCard &card, const QString &setName)
|
||||
{
|
||||
SetToPrintingsMap setsToPrintings = card.getInfo().getSets();
|
||||
|
||||
if (!setsToPrintings.contains(setName))
|
||||
if (!setsToPrintings.contains(setName)) {
|
||||
return PrintingInfo();
|
||||
}
|
||||
|
||||
for (const auto &printing : setsToPrintings[setName]) {
|
||||
if (printing.getUuid() == card.getPrinting().getUuid()) {
|
||||
|
|
|
|||
|
|
@ -181,6 +181,11 @@ bool DeckLoader::saveToNewFile(LoadedDeck &deck, const QString &fileName, DeckFi
|
|||
*/
|
||||
bool DeckLoader::updateLastLoadedTimestamp(LoadedDeck &deck)
|
||||
{
|
||||
// text format doesn't support lastLoadedTimestamp, so there's no point in proceeding
|
||||
if (deck.lastLoadInfo.fileFormat != DeckFileFormat::Cockatrice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString fileName = deck.lastLoadInfo.fileName;
|
||||
|
||||
QFileInfo fileInfo(fileName);
|
||||
|
|
@ -201,15 +206,8 @@ bool DeckLoader::updateLastLoadedTimestamp(LoadedDeck &deck)
|
|||
bool result = false;
|
||||
|
||||
// Perform file modifications
|
||||
switch (deck.lastLoadInfo.fileFormat) {
|
||||
case DeckFileFormat::PlainText:
|
||||
result = deck.deckList.saveToFile_Plain(&file);
|
||||
break;
|
||||
case DeckFileFormat::Cockatrice:
|
||||
deck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
|
||||
result = deck.deckList.saveToFile_Native(&file);
|
||||
break;
|
||||
}
|
||||
deck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
|
||||
result = deck.deckList.saveToFile_Native(&file);
|
||||
|
||||
file.close(); // Close the file to ensure changes are flushed
|
||||
|
||||
|
|
@ -429,8 +427,7 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
|
|||
}
|
||||
if (addSetNameAndNumber) {
|
||||
if (!card->getCardSetShortName().isNull() && !card->getCardSetShortName().isEmpty()) {
|
||||
out << " "
|
||||
<< "(" << card->getCardSetShortName() << ")";
|
||||
out << " " << "(" << card->getCardSetShortName() << ")";
|
||||
}
|
||||
if (!card->getCardCollectorNumber().isNull()) {
|
||||
out << " " << card->getCardCollectorNumber();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file deck_loader.h
|
||||
* @ingroup ImportExport
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DECK_LOADER_H
|
||||
#define DECK_LOADER_H
|
||||
|
|
|
|||
|
|
@ -6,29 +6,33 @@ bool KeySignals::eventFilter(QObject * /*object*/, QEvent *event)
|
|||
{
|
||||
QKeyEvent *kevent;
|
||||
|
||||
if (event->type() != QEvent::KeyPress)
|
||||
if (event->type() != QEvent::KeyPress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
kevent = static_cast<QKeyEvent *>(event);
|
||||
switch (kevent->key()) {
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
emit onCtrlAltEnter();
|
||||
else if (kevent->modifiers() & Qt::ControlModifier)
|
||||
} else if (kevent->modifiers() & Qt::ControlModifier) {
|
||||
emit onCtrlEnter();
|
||||
else
|
||||
} else {
|
||||
emit onEnter();
|
||||
}
|
||||
|
||||
break;
|
||||
case Qt::Key_Right:
|
||||
if (kevent->modifiers() & Qt::ShiftModifier)
|
||||
if (kevent->modifiers() & Qt::ShiftModifier) {
|
||||
emit onShiftRight();
|
||||
}
|
||||
|
||||
break;
|
||||
case Qt::Key_Left:
|
||||
if (kevent->modifiers() & Qt::ShiftModifier)
|
||||
if (kevent->modifiers() & Qt::ShiftModifier) {
|
||||
emit onShiftLeft();
|
||||
}
|
||||
|
||||
break;
|
||||
case Qt::Key_Delete:
|
||||
|
|
@ -37,33 +41,39 @@ bool KeySignals::eventFilter(QObject * /*object*/, QEvent *event)
|
|||
|
||||
break;
|
||||
case Qt::Key_Minus:
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
emit onCtrlAltMinus();
|
||||
}
|
||||
|
||||
break;
|
||||
case Qt::Key_Equal:
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
emit onCtrlAltEqual();
|
||||
}
|
||||
|
||||
break;
|
||||
case Qt::Key_BracketLeft:
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
emit onCtrlAltLBracket();
|
||||
}
|
||||
|
||||
break;
|
||||
case Qt::Key_BracketRight:
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
emit onCtrlAltRBracket();
|
||||
}
|
||||
|
||||
break;
|
||||
case Qt::Key_S:
|
||||
if (kevent->modifiers() & Qt::ShiftModifier)
|
||||
if (kevent->modifiers() & Qt::ShiftModifier) {
|
||||
emit onShiftS();
|
||||
}
|
||||
|
||||
break;
|
||||
case Qt::Key_C:
|
||||
if (kevent->modifiers() & Qt::ControlModifier)
|
||||
if (kevent->modifiers() & Qt::ControlModifier) {
|
||||
emit onCtrlC();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* @file key_signals.h
|
||||
* @ingroup Core
|
||||
* @ingroup UI
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef KEYSIGNALS_H
|
||||
#define KEYSIGNALS_H
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
/**
|
||||
* @file flow_layout.cpp
|
||||
* @brief Implementation of the FlowLayout class, a custom layout for dynamically organizing widgets
|
||||
* in rows within the constraints of available width or parent scroll areas.
|
||||
* @brief Implementation of FlowLayout — a QLayout that wraps child widgets into rows
|
||||
* (Qt::Horizontal flow) or columns (Qt::Vertical flow).
|
||||
*
|
||||
* Design contract (following Qt layout conventions):
|
||||
* - setGeometry() places children inside the given rect. Nothing else.
|
||||
* - sizeHint() reports the unconstrained preferred size (all items in one line).
|
||||
* - minimumSize() reports the minimum size (largest single item).
|
||||
* - heightForWidth() reports the height needed for a given width (horizontal flow only).
|
||||
*
|
||||
* The layout never calls setFixedSize() or adjustSize() on its parent widget;
|
||||
* that is the responsibility of the parent widget / scroll area.
|
||||
*/
|
||||
|
||||
#include "flow_layout.h"
|
||||
|
|
@ -12,27 +21,18 @@
|
|||
#include <QLayoutItem>
|
||||
#include <QScrollArea>
|
||||
#include <QStyle>
|
||||
#include <QWidgetItem>
|
||||
|
||||
/**
|
||||
* @brief Constructs a FlowLayout instance with the specified parent widget, margin, and spacing values.
|
||||
* @param parent The parent widget for this layout.
|
||||
* @param margin The layout margin.
|
||||
* @param hSpacing The horizontal spacing between items.
|
||||
* @param vSpacing The vertical spacing between items.
|
||||
*/
|
||||
FlowLayout::FlowLayout(QWidget *parent,
|
||||
const Qt::Orientation _flowDirection,
|
||||
const Qt::Orientation flowDirection,
|
||||
const int margin,
|
||||
const int hSpacing,
|
||||
const int vSpacing)
|
||||
: QLayout(parent), flowDirection(_flowDirection), horizontalMargin(hSpacing), verticalMargin(vSpacing)
|
||||
: QLayout(parent), flowDirection(flowDirection), horizontalMargin(hSpacing), verticalMargin(vSpacing)
|
||||
{
|
||||
setContentsMargins(margin, margin, margin, margin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor for FlowLayout, which cleans up all items in the layout.
|
||||
*/
|
||||
FlowLayout::~FlowLayout()
|
||||
{
|
||||
QLayoutItem *item;
|
||||
|
|
@ -42,499 +42,353 @@ FlowLayout::~FlowLayout()
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Indicates the layout's support for expansion in both horizontal and vertical directions.
|
||||
* @return The orientations (Qt::Horizontal | Qt::Vertical) this layout can expand to fill.
|
||||
* @brief Reports which axis the layout can expand along.
|
||||
*
|
||||
* A horizontally-flowing layout expands horizontally (and wraps vertically,
|
||||
* but that is governed by heightForWidth, not by this flag).
|
||||
* A vertically-flowing layout expands vertically.
|
||||
*/
|
||||
Qt::Orientations FlowLayout::expandingDirections() const
|
||||
{
|
||||
return Qt::Horizontal | Qt::Vertical;
|
||||
return (flowDirection == Qt::Horizontal) ? Qt::Horizontal : Qt::Vertical;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Indicates that this layout's height depends on its width.
|
||||
* @return True, as the layout adjusts its height to fit the specified width.
|
||||
* @brief Height-for-width is only meaningful for horizontal (wrapping) flow.
|
||||
*/
|
||||
bool FlowLayout::hasHeightForWidth() const
|
||||
{
|
||||
return true;
|
||||
return flowDirection == Qt::Horizontal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the required height to display all items within the specified width.
|
||||
* @param width The available width for arranging items.
|
||||
* @return The total height needed to fit all items in rows constrained by the specified width.
|
||||
* @brief Returns the height required to display all items within @p width.
|
||||
*
|
||||
* Only valid for horizontal flow; returns -1 otherwise so Qt ignores it.
|
||||
* Spacing is counted once between adjacent items, never before the first
|
||||
* or after the last.
|
||||
*/
|
||||
int FlowLayout::heightForWidth(const int width) const
|
||||
{
|
||||
if (flowDirection == Qt::Vertical) {
|
||||
int height = 0;
|
||||
int rowWidth = 0;
|
||||
int rowHeight = 0;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int itemWidth = item->sizeHint().width() + horizontalSpacing();
|
||||
if (rowWidth + itemWidth > width) {
|
||||
height += rowHeight + verticalSpacing();
|
||||
rowWidth = itemWidth;
|
||||
rowHeight = item->sizeHint().height();
|
||||
} else {
|
||||
rowWidth += itemWidth;
|
||||
rowHeight = qMax(rowHeight, item->sizeHint().height());
|
||||
}
|
||||
}
|
||||
height += rowHeight; // Add height of the last row
|
||||
return height;
|
||||
} else {
|
||||
int width = 0;
|
||||
int colWidth = 0;
|
||||
int colHeight = 0;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int itemHeight = item->sizeHint().height();
|
||||
if (colHeight + itemHeight > width) {
|
||||
width += colWidth;
|
||||
colHeight = itemHeight;
|
||||
colWidth = item->sizeHint().width();
|
||||
} else {
|
||||
colHeight += itemHeight;
|
||||
colWidth = qMax(colWidth, item->sizeHint().width());
|
||||
}
|
||||
}
|
||||
width += colWidth; // Add width of the last column
|
||||
return width;
|
||||
if (flowDirection != Qt::Horizontal) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arranges layout items in rows within the specified rectangle bounds.
|
||||
* @param rect The area within which to position layout items.
|
||||
*/
|
||||
void FlowLayout::setGeometry(const QRect &rect)
|
||||
{
|
||||
QLayout::setGeometry(rect); // Sets the geometry of the layout based on the given rectangle.
|
||||
int totalHeight = 0;
|
||||
int rowUsedWidth = 0;
|
||||
int rowHeight = 0;
|
||||
|
||||
if (flowDirection == Qt::Horizontal) {
|
||||
// If we have a parent scroll area, we're clamped to that, else we use our own rectangle.
|
||||
const int availableWidth = getParentScrollAreaWidth() == 0 ? rect.width() : getParentScrollAreaWidth();
|
||||
|
||||
const int totalHeight = layoutAllRows(rect.x(), rect.y(), availableWidth);
|
||||
|
||||
if (QWidget *parentWidgetPtr = parentWidget()) {
|
||||
parentWidgetPtr->setFixedSize(availableWidth, totalHeight);
|
||||
}
|
||||
} else {
|
||||
const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight());
|
||||
|
||||
const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight);
|
||||
|
||||
if (QWidget *parentWidgetPtr = parentWidget()) {
|
||||
parentWidgetPtr->setFixedSize(totalWidth, availableHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lays out items into rows according to the available width, starting from a given origin.
|
||||
* Each row is arranged within `availableWidth`, wrapping to a new row as necessary.
|
||||
* @param originX The x-coordinate for the layout start position.
|
||||
* @param originY The y-coordinate for the layout start position.
|
||||
* @param availableWidth The width within which each row is constrained.
|
||||
* @return The total height after arranging all rows.
|
||||
*/
|
||||
int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
|
||||
{
|
||||
QVector<QLayoutItem *> rowItems; // Holds items for the current row
|
||||
int currentXPosition = originX; // Tracks the x-coordinate while placing items
|
||||
int currentYPosition = originY; // Tracks the y-coordinate, moving down after each row
|
||||
|
||||
int rowHeight = 0; // Tracks the maximum height of items in the current row
|
||||
|
||||
for (QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint(); // The suggested size for the current item
|
||||
int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing
|
||||
const QSize itemSize = item->sizeHint();
|
||||
// Spacing is only inserted between items, not before the first.
|
||||
const int spaceX = (rowUsedWidth > 0) ? horizontalSpacing() : 0;
|
||||
|
||||
// Check if the current item fits in the remaining width of the current row
|
||||
if (currentXPosition + itemWidth > availableWidth) {
|
||||
// If not, layout the current row and start a new row
|
||||
layoutSingleRow(rowItems, originX, currentYPosition);
|
||||
rowItems.clear(); // Reset the list for the new row
|
||||
currentXPosition = originX; // Reset x-position to the row's start
|
||||
currentYPosition += rowHeight + verticalSpacing(); // Move y-position down to the next row
|
||||
rowHeight = 0; // Reset row height for the new row
|
||||
if (rowUsedWidth > 0 && rowUsedWidth + spaceX + itemSize.width() > width) {
|
||||
// This item overflows the current row — commit the row and start a new one.
|
||||
totalHeight += rowHeight + verticalSpacing();
|
||||
rowUsedWidth = itemSize.width();
|
||||
rowHeight = itemSize.height();
|
||||
} else {
|
||||
rowUsedWidth += spaceX + itemSize.width();
|
||||
rowHeight = qMax(rowHeight, itemSize.height());
|
||||
}
|
||||
}
|
||||
|
||||
return totalHeight + rowHeight; // Include the final (possibly only) row.
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Places all children within @p rect.
|
||||
*
|
||||
* This is the only method that may move/resize children. It does NOT resize
|
||||
* the parent widget; that would break Qt's layout protocol.
|
||||
*/
|
||||
void FlowLayout::setGeometry(const QRect &rect)
|
||||
{
|
||||
QLayout::setGeometry(rect);
|
||||
|
||||
if (flowDirection == Qt::Horizontal) {
|
||||
layoutAllRows(rect.x(), rect.y(), rect.width());
|
||||
} else {
|
||||
layoutAllColumns(rect.x(), rect.y(), rect.height());
|
||||
}
|
||||
}
|
||||
|
||||
QSize FlowLayout::sizeHint() const
|
||||
{
|
||||
return (flowDirection == Qt::Horizontal) ? calculateSizeHintHorizontal() : calculateSizeHintVertical();
|
||||
}
|
||||
|
||||
QSize FlowLayout::minimumSize() const
|
||||
{
|
||||
return (flowDirection == Qt::Horizontal) ? calculateMinimumSizeHorizontal() : calculateMinimumSizeVertical();
|
||||
}
|
||||
|
||||
// ─── Row layout (horizontal flow) ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief Places all items into wrapping rows within @p availableWidth.
|
||||
* @return The y-coordinate of the bottom edge of the last row.
|
||||
*/
|
||||
int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
|
||||
{
|
||||
QVector<QLayoutItem *> rowItems;
|
||||
int rowUsedWidth = 0; // Width consumed by items already in the current row.
|
||||
int currentY = originY;
|
||||
int rowHeight = 0;
|
||||
|
||||
for (QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QSize itemSize = item->sizeHint();
|
||||
// No leading space for the first item in a row.
|
||||
const int spaceX = rowItems.isEmpty() ? 0 : horizontalSpacing();
|
||||
|
||||
if (!rowItems.isEmpty() && rowUsedWidth + spaceX + itemSize.width() > availableWidth) {
|
||||
// Current item does not fit — flush the current row, begin a new one.
|
||||
layoutSingleRow(rowItems, originX, currentY, availableWidth);
|
||||
rowItems.clear();
|
||||
currentY += rowHeight + verticalSpacing();
|
||||
rowUsedWidth = 0;
|
||||
rowHeight = 0;
|
||||
}
|
||||
|
||||
// Add the item to the current row
|
||||
rowItems.append(item);
|
||||
rowHeight = qMax(rowHeight, itemSize.height()); // Update the row's height to the tallest item
|
||||
currentXPosition += itemWidth + horizontalSpacing(); // Move x-position for the next item
|
||||
// `rowItems.size() > 1` is equivalent to "this is not the first item in the row"
|
||||
// because we just appended above.
|
||||
rowUsedWidth += (rowItems.size() > 1 ? horizontalSpacing() : 0) + itemSize.width();
|
||||
rowHeight = qMax(rowHeight, itemSize.height());
|
||||
}
|
||||
|
||||
// Layout the final row if there are any remaining items
|
||||
layoutSingleRow(rowItems, originX, currentYPosition);
|
||||
|
||||
// Return the total height used, including the last row's height
|
||||
return currentYPosition + rowHeight;
|
||||
layoutSingleRow(rowItems, originX, currentY, availableWidth); // Flush the final row.
|
||||
return currentY + rowHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arranges a single row of items within specified x and y starting positions.
|
||||
* @param rowItems A list of items to be arranged in the row.
|
||||
* @param x The starting x-coordinate for the row.
|
||||
* @param y The starting y-coordinate for the row.
|
||||
* @brief Sets the geometry for every item in @p rowItems, starting at (@p x, @p y).
|
||||
*
|
||||
* Items whose horizontal size policy includes the Expand or MinimumExpanding flag
|
||||
* share the leftover row width proportionally (like QHBoxLayout stretch), so that
|
||||
* e.g. a QLineEdit can fill remaining space while fixed-size buttons stay compact.
|
||||
*
|
||||
* Items without an expanding policy are placed at their sizeHint, clamped to maximumSize.
|
||||
*/
|
||||
void FlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y)
|
||||
void FlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y, const int availableWidth)
|
||||
{
|
||||
if (rowItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Pass 1: measure fixed width and count expanding items ────────────────
|
||||
int fixedWidth = 0;
|
||||
int expandingCount = 0;
|
||||
int spacingTotal = (rowItems.size() - 1) * horizontalSpacing();
|
||||
|
||||
for (QLayoutItem *item : rowItems) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
if (!item || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the maximum allowed size for the item
|
||||
QSize itemMaxSize = item->widget()->maximumSize();
|
||||
// Constrain the item's width and height to its size hint or maximum size
|
||||
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
|
||||
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
|
||||
// Set the item's geometry based on the computed size and position
|
||||
QWidget *widget = item->widget();
|
||||
const QSizePolicy::Policy hPolicy = widget ? widget->sizePolicy().horizontalPolicy() : QSizePolicy::Fixed;
|
||||
|
||||
if (hPolicy & QSizePolicy::ExpandFlag) {
|
||||
++expandingCount;
|
||||
} else {
|
||||
const int maxW = widget ? widget->maximumWidth() : QWIDGETSIZE_MAX;
|
||||
fixedWidth += qMin(item->sizeHint().width(), maxW);
|
||||
}
|
||||
}
|
||||
|
||||
// Extra pixels to share among expanding items (never negative).
|
||||
const int extra = qMax(0, availableWidth - spacingTotal - fixedWidth);
|
||||
const int expandingShare = (expandingCount > 0) ? extra / expandingCount : 0;
|
||||
|
||||
// ── Pass 2: place items ──────────────────────────────────────────────────
|
||||
for (QLayoutItem *item : rowItems) {
|
||||
if (!item || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QWidget *widget = item->widget();
|
||||
if (!widget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QSizePolicy::Policy hPolicy = widget->sizePolicy().horizontalPolicy();
|
||||
const QSize maxSize = widget->maximumSize();
|
||||
const bool expands = hPolicy & QSizePolicy::ExpandFlag;
|
||||
|
||||
const int itemWidth =
|
||||
expands ? qMin(expandingShare, maxSize.width()) : qMin(item->sizeHint().width(), maxSize.width());
|
||||
const int itemHeight = qMin(item->sizeHint().height(), maxSize.height());
|
||||
|
||||
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
|
||||
// Move the x-position to the right, leaving space for horizontal spacing
|
||||
x += itemWidth + horizontalSpacing();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Column layout (vertical flow) ───────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief Lays out items into columns according to the available height, starting from a given origin.
|
||||
* Each column is arranged within `availableHeight`, wrapping to a new column as necessary.
|
||||
* @param originX The x-coordinate for the layout start position.
|
||||
* @param originY The y-coordinate for the layout start position.
|
||||
* @param availableHeight The height within which each column is constrained.
|
||||
* @return The total width after arranging all columns.
|
||||
* @brief Places all items into wrapping columns within @p availableHeight.
|
||||
* @return The x-coordinate of the right edge of the last column.
|
||||
*/
|
||||
int FlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight)
|
||||
{
|
||||
QVector<QLayoutItem *> colItems; // Holds items for the current column
|
||||
int currentXPosition = originX; // Tracks the x-coordinate while placing items
|
||||
int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column
|
||||
|
||||
int colWidth = 0; // Tracks the maximum width of items in the current column
|
||||
QVector<QLayoutItem *> colItems;
|
||||
int colUsedHeight = 0; // Height consumed by items already in the current column.
|
||||
int currentX = originX;
|
||||
int colWidth = 0;
|
||||
|
||||
for (QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
if (!item || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint(); // The suggested size for the current item
|
||||
const QSize itemSize = item->sizeHint();
|
||||
// No leading space for the first item in a column.
|
||||
const int spaceY = colItems.isEmpty() ? 0 : verticalSpacing();
|
||||
|
||||
// Check if the current item fits in the remaining height of the current column
|
||||
if (currentYPosition + itemSize.height() > availableHeight) {
|
||||
// If not, layout the current column and start a new column
|
||||
layoutSingleColumn(colItems, currentXPosition, originY);
|
||||
colItems.clear(); // Reset the list for the new column
|
||||
currentYPosition = originY; // Reset y-position to the column's start
|
||||
currentXPosition += colWidth; // Move x-position to the next column
|
||||
colWidth = 0; // Reset column width for the new column
|
||||
if (!colItems.isEmpty() && colUsedHeight + spaceY + itemSize.height() > availableHeight) {
|
||||
// Current item does not fit — flush the current column, begin a new one.
|
||||
layoutSingleColumn(colItems, currentX, originY);
|
||||
colItems.clear();
|
||||
currentX += colWidth + horizontalSpacing();
|
||||
colUsedHeight = 0;
|
||||
colWidth = 0;
|
||||
}
|
||||
|
||||
// Add the item to the current column
|
||||
colItems.append(item);
|
||||
colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item
|
||||
currentYPosition += itemSize.height(); // Move y-position for the next item
|
||||
colUsedHeight += (colItems.size() > 1 ? verticalSpacing() : 0) + itemSize.height();
|
||||
colWidth = qMax(colWidth, itemSize.width());
|
||||
}
|
||||
|
||||
// Layout the final column if there are any remaining items
|
||||
layoutSingleColumn(colItems, currentXPosition, originY);
|
||||
|
||||
// Return the total width used, including the last column's width
|
||||
return currentXPosition + colWidth;
|
||||
layoutSingleColumn(colItems, currentX, originY); // Flush the final column.
|
||||
return currentX + colWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arranges a single column of items within specified x and y starting positions.
|
||||
* @param colItems A list of items to be arranged in the column.
|
||||
* @param x The starting x-coordinate for the column.
|
||||
* @param y The starting y-coordinate for the column.
|
||||
* @brief Sets the geometry for every item in @p colItems, starting at (@p x, @p y).
|
||||
*
|
||||
* Each item is placed at its sizeHint, clamped to its maximumSize.
|
||||
*/
|
||||
void FlowLayout::layoutSingleColumn(const QVector<QLayoutItem *> &colItems, const int x, int y)
|
||||
{
|
||||
for (QLayoutItem *item : colItems) {
|
||||
if (item == nullptr) {
|
||||
qCDebug(FlowLayoutLog) << "Item is null.";
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping null or empty item in column.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Debugging: Print the item's widget class name and size hint
|
||||
QWidget *widget = item->widget();
|
||||
if (widget) {
|
||||
qCDebug(FlowLayoutLog) << "Widget class:" << widget->metaObject()->className();
|
||||
qCDebug(FlowLayoutLog) << "Widget size hint:" << widget->sizeHint();
|
||||
qCDebug(FlowLayoutLog) << "Widget maximum size:" << widget->maximumSize();
|
||||
qCDebug(FlowLayoutLog) << "Widget minimum size:" << widget->minimumSize();
|
||||
|
||||
// Debugging: Print child widgets
|
||||
const QObjectList &children = widget->children();
|
||||
qCDebug(FlowLayoutLog) << "Child widgets:";
|
||||
for (QObject *child : children) {
|
||||
if (QWidget *childWidget = qobject_cast<QWidget *>(child)) {
|
||||
qCDebug(FlowLayoutLog) << " - Child widget class:" << childWidget->metaObject()->className();
|
||||
qCDebug(FlowLayoutLog) << " Size hint:" << childWidget->sizeHint();
|
||||
qCDebug(FlowLayoutLog) << " Maximum size:" << childWidget->maximumSize();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCDebug(FlowLayoutLog) << "Item does not have a widget.";
|
||||
if (!widget) {
|
||||
qCDebug(FlowLayoutLog) << "Item has no widget; skipping.";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the maximum allowed size for the item
|
||||
QSize itemMaxSize = widget->maximumSize();
|
||||
// Constrain the item's width and height to its size hint or maximum size
|
||||
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
|
||||
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
|
||||
// Debugging: Print the computed geometry
|
||||
qCDebug(FlowLayoutLog) << "Computed geometry: x=" << x << ", y=" << y << ", width=" << itemWidth
|
||||
<< ", height=" << itemHeight;
|
||||
qCDebug(FlowLayoutLog) << "Widget:" << widget->metaObject()->className() << "sizeHint:" << widget->sizeHint()
|
||||
<< "maximumSize:" << widget->maximumSize() << "minimumSize:" << widget->minimumSize();
|
||||
|
||||
const QSize maxSize = widget->maximumSize();
|
||||
const int itemWidth = qMin(item->sizeHint().width(), maxSize.width());
|
||||
const int itemHeight = qMin(item->sizeHint().height(), maxSize.height());
|
||||
|
||||
qCDebug(FlowLayoutLog) << "Placing at x=" << x << "y=" << y << "w=" << itemWidth << "h=" << itemHeight;
|
||||
|
||||
// Set the item's geometry based on the computed size and position
|
||||
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
|
||||
|
||||
// Move the y-position down by the item's height to place the next item below
|
||||
y += itemHeight;
|
||||
y += itemHeight + verticalSpacing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the preferred size of the layout based on the flow direction.
|
||||
* @return A QSize representing the ideal dimensions of the layout.
|
||||
*/
|
||||
QSize FlowLayout::sizeHint() const
|
||||
{
|
||||
if (flowDirection == Qt::Horizontal) {
|
||||
return calculateSizeHintHorizontal();
|
||||
} else {
|
||||
return calculateSizeHintVertical();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the minimum size required by the layout based on the flow direction.
|
||||
* @return A QSize representing the minimum required dimensions.
|
||||
*/
|
||||
QSize FlowLayout::minimumSize() const
|
||||
{
|
||||
if (flowDirection == Qt::Horizontal) {
|
||||
return calculateMinimumSizeHorizontal();
|
||||
} else {
|
||||
return calculateMinimumSizeVertical();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the size hint for horizontal flow direction.
|
||||
* @return A QSize representing the preferred dimensions.
|
||||
* @brief Preferred size for horizontal flow: all items in a single row (unconstrained).
|
||||
*
|
||||
* The actual displayed height is determined by heightForWidth() once Qt knows the
|
||||
* real available width.
|
||||
*/
|
||||
QSize FlowLayout::calculateSizeHintHorizontal() const
|
||||
{
|
||||
int maxWidth = 0; // Tracks the maximum width needed
|
||||
int totalHeight = 0; // Tracks the total height across all rows
|
||||
int rowHeight = 0; // Tracks the height of the current row
|
||||
int currentWidth = 0; // Tracks the current row's width
|
||||
|
||||
const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth();
|
||||
|
||||
qCDebug(FlowLayoutLog) << "Calculating horizontal size hint. Available width:" << availableWidth;
|
||||
int totalWidth = 0;
|
||||
int maxHeight = 0;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint();
|
||||
int itemWidth = itemSize.width() + horizontalSpacing();
|
||||
qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize << "Width with spacing:" << itemWidth;
|
||||
|
||||
if (currentWidth + itemWidth > availableWidth) {
|
||||
qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight;
|
||||
maxWidth = qMax(maxWidth, currentWidth);
|
||||
totalHeight += rowHeight + verticalSpacing();
|
||||
qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth;
|
||||
|
||||
currentWidth = 0;
|
||||
rowHeight = 0;
|
||||
const QSize s = item->sizeHint();
|
||||
if (totalWidth > 0) {
|
||||
totalWidth += horizontalSpacing();
|
||||
}
|
||||
|
||||
currentWidth += itemWidth;
|
||||
rowHeight = qMax(rowHeight, itemSize.height());
|
||||
qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight;
|
||||
totalWidth += s.width();
|
||||
maxHeight = qMax(maxHeight, s.height());
|
||||
}
|
||||
|
||||
// Account for the final row
|
||||
maxWidth = qMax(maxWidth, currentWidth);
|
||||
totalHeight += rowHeight;
|
||||
qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth;
|
||||
|
||||
return QSize(maxWidth, totalHeight);
|
||||
return QSize(totalWidth, maxHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the minimum size for horizontal flow direction.
|
||||
* @return A QSize representing the minimum required dimensions.
|
||||
* @brief Minimum size for horizontal flow: the largest single item.
|
||||
*
|
||||
* This guarantees we can always display at least one item per row.
|
||||
*/
|
||||
QSize FlowLayout::calculateMinimumSizeHorizontal() const
|
||||
{
|
||||
int maxWidth = 0; // Tracks the maximum width of a row
|
||||
int totalHeight = 0; // Tracks the total height across all rows
|
||||
int rowHeight = 0; // Tracks the height of the current row
|
||||
int currentWidth = 0; // Tracks the current row's width
|
||||
QSize size(0, 0);
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
size = size.expandedTo(item->minimumSize());
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth();
|
||||
|
||||
qCDebug(FlowLayoutLog) << "Calculating horizontal minimum size. Available width:" << availableWidth;
|
||||
/**
|
||||
* @brief Preferred size for vertical flow: all items in a single column (unconstrained).
|
||||
*/
|
||||
QSize FlowLayout::calculateSizeHintVertical() const
|
||||
{
|
||||
int maxWidth = 0;
|
||||
int totalHeight = 0;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemMinSize = item->minimumSize();
|
||||
int itemWidth = itemMinSize.width() + horizontalSpacing();
|
||||
qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize << "Width with spacing:" << itemWidth;
|
||||
|
||||
if (currentWidth + itemWidth > availableWidth) {
|
||||
qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight;
|
||||
maxWidth = qMax(maxWidth, currentWidth);
|
||||
totalHeight += rowHeight + verticalSpacing();
|
||||
qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth;
|
||||
|
||||
currentWidth = 0;
|
||||
rowHeight = 0;
|
||||
const QSize s = item->sizeHint();
|
||||
if (totalHeight > 0) {
|
||||
totalHeight += verticalSpacing();
|
||||
}
|
||||
|
||||
currentWidth += itemWidth;
|
||||
rowHeight = qMax(rowHeight, itemMinSize.height());
|
||||
qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight;
|
||||
totalHeight += s.height();
|
||||
maxWidth = qMax(maxWidth, s.width());
|
||||
}
|
||||
|
||||
// Account for the final row
|
||||
maxWidth = qMax(maxWidth, currentWidth);
|
||||
totalHeight += rowHeight;
|
||||
qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth;
|
||||
|
||||
return QSize(maxWidth, totalHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the size hint for vertical flow direction.
|
||||
* @return A QSize representing the preferred dimensions.
|
||||
*/
|
||||
QSize FlowLayout::calculateSizeHintVertical() const
|
||||
{
|
||||
int totalWidth = 0;
|
||||
int maxHeight = 0;
|
||||
int colWidth = 0;
|
||||
int currentHeight = 0;
|
||||
|
||||
const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight());
|
||||
|
||||
qCDebug(FlowLayoutLog) << "Calculating vertical size hint. Available height:" << availableHeight;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint();
|
||||
qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize;
|
||||
|
||||
if (currentHeight + itemSize.height() > availableHeight) {
|
||||
qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight
|
||||
<< "Column width:" << colWidth;
|
||||
totalWidth += colWidth + horizontalSpacing();
|
||||
maxHeight = qMax(maxHeight, currentHeight);
|
||||
qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight;
|
||||
|
||||
currentHeight = 0;
|
||||
colWidth = 0;
|
||||
}
|
||||
|
||||
currentHeight += itemSize.height() + verticalSpacing();
|
||||
colWidth = qMax(colWidth, itemSize.width());
|
||||
qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth;
|
||||
}
|
||||
|
||||
// Account for the final column
|
||||
totalWidth += colWidth;
|
||||
maxHeight = qMax(maxHeight, currentHeight);
|
||||
qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight;
|
||||
|
||||
return QSize(totalWidth, maxHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the minimum size for vertical flow direction.
|
||||
* @return A QSize representing the minimum required dimensions.
|
||||
* @brief Minimum size for vertical flow: the largest single item.
|
||||
*/
|
||||
QSize FlowLayout::calculateMinimumSizeVertical() const
|
||||
{
|
||||
int totalWidth = 0; // Tracks the total width across all columns
|
||||
int maxHeight = 0; // Tracks the maximum height of a column
|
||||
int colWidth = 0; // Tracks the width of the current column
|
||||
int currentHeight = 0; // Tracks the current column's height
|
||||
|
||||
const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight());
|
||||
|
||||
qCDebug(FlowLayoutLog) << "Calculating vertical minimum size. Available height:" << availableHeight;
|
||||
|
||||
QSize size(0, 0);
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemMinSize = item->minimumSize();
|
||||
int itemHeight = itemMinSize.height() + verticalSpacing();
|
||||
qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize
|
||||
<< "Height with spacing:" << itemHeight;
|
||||
|
||||
if (currentHeight + itemHeight > availableHeight) {
|
||||
qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight
|
||||
<< "Column width:" << colWidth;
|
||||
totalWidth += colWidth + horizontalSpacing();
|
||||
maxHeight = qMax(maxHeight, currentHeight);
|
||||
qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight;
|
||||
|
||||
currentHeight = 0;
|
||||
colWidth = 0;
|
||||
}
|
||||
|
||||
currentHeight += itemHeight;
|
||||
colWidth = qMax(colWidth, itemMinSize.width());
|
||||
qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth;
|
||||
size = size.expandedTo(item->minimumSize());
|
||||
}
|
||||
|
||||
// Account for the final column
|
||||
totalWidth += colWidth;
|
||||
maxHeight = qMax(maxHeight, currentHeight);
|
||||
qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight;
|
||||
|
||||
return QSize(totalWidth, maxHeight);
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -543,7 +397,7 @@ QSize FlowLayout::calculateMinimumSizeVertical() const
|
|||
*/
|
||||
void FlowLayout::addItem(QLayoutItem *item)
|
||||
{
|
||||
if (item != nullptr) {
|
||||
if (item) {
|
||||
items.append(item);
|
||||
}
|
||||
}
|
||||
|
|
@ -551,11 +405,8 @@ void FlowLayout::addItem(QLayoutItem *item)
|
|||
void FlowLayout::insertWidgetAtIndex(QWidget *toInsert, int index)
|
||||
{
|
||||
addChildWidget(toInsert);
|
||||
|
||||
// We don't want to fail on an index that violates the bounds, so we just clamp it.
|
||||
int boundedIndex = qBound(0, index, qMax(0, static_cast<int>(items.size())));
|
||||
items.insert(boundedIndex, new QWidgetItem(toInsert));
|
||||
|
||||
const int bounded = qBound(0, index, static_cast<int>(items.size()));
|
||||
items.insert(bounded, new QWidgetItem(toInsert));
|
||||
invalidate();
|
||||
}
|
||||
|
||||
|
|
@ -613,52 +464,13 @@ int FlowLayout::verticalSpacing() const
|
|||
*/
|
||||
int FlowLayout::smartSpacing(const QStyle::PixelMetric pm) const
|
||||
{
|
||||
QObject *parent = this->parent();
|
||||
|
||||
if (!parent) {
|
||||
QObject *p = parent();
|
||||
if (!p) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (parent->isWidgetType()) {
|
||||
const auto *pw = dynamic_cast<QWidget *>(parent);
|
||||
if (p->isWidgetType()) {
|
||||
const auto *pw = static_cast<QWidget *>(p);
|
||||
return pw->style()->pixelMetric(pm, nullptr, pw);
|
||||
}
|
||||
|
||||
return dynamic_cast<QLayout *>(parent)->spacing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the width of the parent scroll area, if any.
|
||||
* @return The width of the scroll area's viewport, or 0 if not found.
|
||||
*/
|
||||
int FlowLayout::getParentScrollAreaWidth() const
|
||||
{
|
||||
QWidget *parent = parentWidget();
|
||||
|
||||
while (parent) {
|
||||
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
|
||||
return scrollArea->viewport()->width();
|
||||
}
|
||||
parent = parent->parentWidget();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the height of the parent scroll area, if any.
|
||||
* @return The height of the scroll area's viewport, or 0 if not found.
|
||||
*/
|
||||
int FlowLayout::getParentScrollAreaHeight() const
|
||||
{
|
||||
QWidget *parent = parentWidget();
|
||||
|
||||
while (parent) {
|
||||
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
|
||||
return scrollArea->viewport()->height();
|
||||
}
|
||||
parent = parent->parentWidget();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
return static_cast<QLayout *>(p)->spacing();
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
/**
|
||||
* @file flow_layout.h
|
||||
* @ingroup UI
|
||||
* @brief TODO: Document this.
|
||||
* @brief A QLayout subclass that arranges child widgets in wrapping rows (horizontal flow)
|
||||
* or wrapping columns (vertical flow).
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef FLOW_LAYOUT_H
|
||||
#define FLOW_LAYOUT_H
|
||||
|
|
@ -10,8 +12,8 @@
|
|||
#include <QLayout>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QStyle>
|
||||
#include <QWidget>
|
||||
#include <qstyle.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(FlowLayoutLog, "flow_layout", QtInfoMsg);
|
||||
|
||||
|
|
@ -19,42 +21,55 @@ class FlowLayout : public QLayout
|
|||
{
|
||||
public:
|
||||
explicit FlowLayout(QWidget *parent = nullptr);
|
||||
FlowLayout(QWidget *parent, Qt::Orientation _flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0);
|
||||
FlowLayout(QWidget *parent, Qt::Orientation flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0);
|
||||
~FlowLayout() override;
|
||||
|
||||
void insertWidgetAtIndex(QWidget *toInsert, int index);
|
||||
|
||||
[[nodiscard]] QSize calculateMinimumSizeHorizontal() const;
|
||||
[[nodiscard]] QSize calculateSizeHintVertical() const;
|
||||
[[nodiscard]] QSize calculateMinimumSizeVertical() const;
|
||||
// QLayout interface
|
||||
void addItem(QLayoutItem *item) override;
|
||||
[[nodiscard]] int count() const override;
|
||||
[[nodiscard]] QLayoutItem *itemAt(int index) const override;
|
||||
QLayoutItem *takeAt(int index) override;
|
||||
[[nodiscard]] int horizontalSpacing() const;
|
||||
void setGeometry(const QRect &rect) override;
|
||||
|
||||
// Size negotiation
|
||||
[[nodiscard]] Qt::Orientations expandingDirections() const override;
|
||||
[[nodiscard]] bool hasHeightForWidth() const override;
|
||||
[[nodiscard]] int heightForWidth(int width) const override;
|
||||
[[nodiscard]] int verticalSpacing() const;
|
||||
[[nodiscard]] int doLayout(const QRect &rect, bool testOnly) const;
|
||||
[[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const;
|
||||
[[nodiscard]] int getParentScrollAreaWidth() const;
|
||||
[[nodiscard]] int getParentScrollAreaHeight() const;
|
||||
|
||||
void setGeometry(const QRect &rect) override;
|
||||
virtual int layoutAllRows(int originX, int originY, int availableWidth);
|
||||
virtual void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y);
|
||||
int layoutAllColumns(int originX, int originY, int availableHeight);
|
||||
void layoutSingleColumn(const QVector<QLayoutItem *> &colItems, int x, int y);
|
||||
[[nodiscard]] QSize sizeHint() const override;
|
||||
[[nodiscard]] QSize minimumSize() const override;
|
||||
[[nodiscard]] QSize calculateSizeHintHorizontal() const;
|
||||
|
||||
// Spacing helpers
|
||||
void setHorizontalMargin(int margin)
|
||||
{
|
||||
horizontalMargin = margin;
|
||||
}
|
||||
[[nodiscard]] int horizontalSpacing() const;
|
||||
void setVerticalMargin(int margin)
|
||||
{
|
||||
verticalMargin = margin;
|
||||
}
|
||||
[[nodiscard]] int verticalSpacing() const;
|
||||
[[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const;
|
||||
|
||||
// Layout passes (virtual so subclasses can override placement logic)
|
||||
virtual int layoutAllRows(int originX, int originY, int availableWidth);
|
||||
virtual void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y, int availableWidth);
|
||||
int layoutAllColumns(int originX, int originY, int availableHeight);
|
||||
void layoutSingleColumn(const QVector<QLayoutItem *> &colItems, int x, int y);
|
||||
|
||||
protected:
|
||||
QList<QLayoutItem *> items; // List to store layout items
|
||||
// Size-hint helpers split by direction
|
||||
[[nodiscard]] QSize calculateSizeHintHorizontal() const;
|
||||
[[nodiscard]] QSize calculateMinimumSizeHorizontal() const;
|
||||
[[nodiscard]] QSize calculateSizeHintVertical() const;
|
||||
[[nodiscard]] QSize calculateMinimumSizeVertical() const;
|
||||
|
||||
QList<QLayoutItem *> items;
|
||||
Qt::Orientation flowDirection;
|
||||
int horizontalMargin;
|
||||
int verticalMargin;
|
||||
int horizontalMargin; ///< Horizontal spacing between items (-1 = use style default)
|
||||
int verticalMargin; ///< Vertical spacing between items (-1 = use style default)
|
||||
};
|
||||
|
||||
#endif // FLOW_LAYOUT_H
|
||||
|
|
@ -223,7 +223,7 @@ void OverlapLayout::setGeometry(const QRect &rect)
|
|||
const int yPos = rect.top() + currentRow * (maxItemHeight - overlapOffsetHeight);
|
||||
item->setGeometry(QRect(xPos, yPos, maxItemWidth, maxItemHeight));
|
||||
|
||||
// TODO: Figure this out properly or maybe adjust size hint to account for this?
|
||||
//! \todo Figure this out properly or maybe adjust size hint to account for this.
|
||||
// Update row and column indices based on the layout direction.
|
||||
if (overlapDirection == Qt::Horizontal) {
|
||||
currentColumn++;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file overlap_layout.h
|
||||
* @ingroup UI
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef OVERLAP_LAYOUT_H
|
||||
#define OVERLAP_LAYOUT_H
|
||||
|
|
|
|||
|
|
@ -66,8 +66,9 @@ void Logger::openLogfileSession()
|
|||
|
||||
void Logger::closeLogfileSession()
|
||||
{
|
||||
if (!logToFileEnabled)
|
||||
if (!logToFileEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
logToFileEnabled = false;
|
||||
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << Qt::endl;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file logger.h
|
||||
* @ingroup Core
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
|
|
|||
72
cockatrice/src/interface/palette_editor/color_button.cpp
Normal file
72
cockatrice/src/interface/palette_editor/color_button.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#include "color_button.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QPainter>
|
||||
|
||||
ColorButton::ColorButton(QWidget *parent) : QToolButton(parent)
|
||||
{
|
||||
setFixedSize(52, 24);
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
setToolTip(tr("Click to pick a color"));
|
||||
connect(this, &QToolButton::clicked, this, &ColorButton::pickColor);
|
||||
}
|
||||
|
||||
void ColorButton::setColor(const QColor &c)
|
||||
{
|
||||
if (color == c) {
|
||||
return;
|
||||
}
|
||||
color = c;
|
||||
updateSwatch();
|
||||
emit colorChanged(c);
|
||||
}
|
||||
|
||||
void ColorButton::pickColor()
|
||||
{
|
||||
QColor chosen = QColorDialog::getColor(color, this, tr("Pick colour"), QColorDialog::ShowAlphaChannel);
|
||||
if (chosen.isValid()) {
|
||||
setColor(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorButton::updateSwatch()
|
||||
{
|
||||
QPixmap pixmap(size() * devicePixelRatioF());
|
||||
pixmap.setDevicePixelRatio(devicePixelRatioF());
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Checkerboard for alpha
|
||||
const int cellSize = 4;
|
||||
for (int y = 0; y < height(); y += cellSize) {
|
||||
for (int x = 0; x < width(); x += cellSize) {
|
||||
painter.fillRect(x, y, cellSize, cellSize,
|
||||
((x / cellSize + y / cellSize) % 2) ? QColor(180, 180, 180) : Qt::white);
|
||||
}
|
||||
}
|
||||
|
||||
// Color fill
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(color);
|
||||
painter.drawRoundedRect(QRectF(0.5, 0.5, width() - 1, height() - 1), 3, 3);
|
||||
|
||||
// Border
|
||||
QColor border = palette().color(QPalette::Shadow);
|
||||
border.setAlpha(180);
|
||||
painter.setPen(QPen(border, 1));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawRoundedRect(QRectF(0.5, 0.5, width() - 1, height() - 1), 3, 3);
|
||||
|
||||
// Hex label — black or white for contrast
|
||||
int luma = 0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue();
|
||||
painter.setPen((luma > 128 && color.alpha() > 80) ? QColor(0, 0, 0, 180) : QColor(255, 255, 255, 200));
|
||||
QFont f = font();
|
||||
f.setPixelSize(8);
|
||||
painter.setFont(f);
|
||||
painter.drawText(rect(), Qt::AlignCenter, color.name().toUpper());
|
||||
|
||||
setIcon(QIcon(pixmap));
|
||||
setIconSize(size());
|
||||
setText({});
|
||||
}
|
||||
30
cockatrice/src/interface/palette_editor/color_button.h
Normal file
30
cockatrice/src/interface/palette_editor/color_button.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef COCKATRICE_COLOR_BUTTON_H
|
||||
#define COCKATRICE_COLOR_BUTTON_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QToolButton>
|
||||
|
||||
class ColorButton : public QToolButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ColorButton(QWidget *parent = nullptr);
|
||||
|
||||
QColor getColor() const
|
||||
{
|
||||
return color;
|
||||
}
|
||||
void setColor(const QColor &c);
|
||||
|
||||
signals:
|
||||
void colorChanged(const QColor &color);
|
||||
|
||||
private slots:
|
||||
void pickColor();
|
||||
|
||||
private:
|
||||
void updateSwatch();
|
||||
QColor color;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COLOR_BUTTON_H
|
||||
|
|
@ -0,0 +1,326 @@
|
|||
#include "palette_editor_dialog.h"
|
||||
|
||||
#include "../theme_manager.h"
|
||||
#include "palette_generator.h"
|
||||
#include "palette_grid_widget.h"
|
||||
#include "quick_setup_panel.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFrame>
|
||||
#include <QGuiApplication>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QStyleHints>
|
||||
#include <QTimer>
|
||||
|
||||
PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QString &_themeName, QWidget *parent)
|
||||
: QDialog(parent), themeDirPath(_themeDirPath), themeName(_themeName)
|
||||
{
|
||||
setMinimumSize(740, 220);
|
||||
setupUi();
|
||||
|
||||
// Load both scheme configs upfront so switching is instant
|
||||
loadSchemes();
|
||||
|
||||
loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light";
|
||||
|
||||
schemeComboBox->blockSignals(true);
|
||||
schemeComboBox->setCurrentText(loadedScheme);
|
||||
schemeComboBox->blockSignals(false);
|
||||
|
||||
paletteGrid->loadPalette(workingConfig[loadedScheme]);
|
||||
seedAccentFromScheme(loadedScheme);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::setupUi()
|
||||
{
|
||||
auto *root = new QVBoxLayout(this);
|
||||
root->setSpacing(0);
|
||||
root->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// Header
|
||||
header = new QWidget;
|
||||
header->setAutoFillBackground(true);
|
||||
{
|
||||
QPalette hp = header->palette();
|
||||
hp.setColor(QPalette::Window, qApp->palette().color(QPalette::Window).darker(108));
|
||||
header->setPalette(hp);
|
||||
}
|
||||
auto *headerLayout = new QHBoxLayout(header);
|
||||
headerLayout->setContentsMargins(12, 8, 12, 8);
|
||||
|
||||
titleLabel = new QLabel(this);
|
||||
titleLabel->setTextFormat(Qt::RichText);
|
||||
|
||||
editingLabel = new QLabel(this);
|
||||
|
||||
schemeComboBox = new QComboBox;
|
||||
schemeComboBox->addItems({"Light", "Dark"});
|
||||
schemeComboBox->setFixedWidth(90);
|
||||
|
||||
headerLayout->addWidget(titleLabel);
|
||||
headerLayout->addStretch();
|
||||
headerLayout->addWidget(editingLabel);
|
||||
headerLayout->addWidget(schemeComboBox);
|
||||
root->addWidget(header);
|
||||
|
||||
auto makeSeparator = [&]() {
|
||||
auto *sep = new QFrame;
|
||||
sep->setFrameShape(QFrame::HLine);
|
||||
sep->setFrameShadow(QFrame::Plain);
|
||||
sep->setFixedHeight(1);
|
||||
return sep;
|
||||
};
|
||||
|
||||
root->addWidget(makeSeparator());
|
||||
|
||||
// Quick Setup panel
|
||||
quickSetupPanel = new QuickSetupPanel;
|
||||
quickSetupPanel->setAutoFillBackground(true);
|
||||
|
||||
QPalette sp = quickSetupPanel->palette();
|
||||
sp.setColor(QPalette::Window, qApp->palette().color(QPalette::Window).darker(102));
|
||||
quickSetupPanel->setPalette(sp);
|
||||
|
||||
root->addWidget(quickSetupPanel);
|
||||
root->addWidget(makeSeparator());
|
||||
|
||||
// Toggle button — acts as a section header for the advanced area
|
||||
paletteGridToggleButton = new QPushButton(this);
|
||||
paletteGridToggleButton->setCheckable(true);
|
||||
paletteGridToggleButton->setChecked(false);
|
||||
paletteGridToggleButton->setFlat(true);
|
||||
paletteGridToggleButton->setStyleSheet("QPushButton { text-align: left; padding: 5px 12px; font-weight: bold; }"
|
||||
"QPushButton:checked { }");
|
||||
root->addWidget(paletteGridToggleButton);
|
||||
|
||||
// Separator + grid start hidden; revealed by the toggle
|
||||
paletteGridSeparator = makeSeparator();
|
||||
paletteGridSeparator->setVisible(false);
|
||||
root->addWidget(paletteGridSeparator);
|
||||
|
||||
paletteGrid = new PaletteGridWidget;
|
||||
paletteGrid->setVisible(false);
|
||||
root->addWidget(paletteGrid, 1);
|
||||
|
||||
// Footer
|
||||
root->addWidget(makeSeparator());
|
||||
footer = new QWidget;
|
||||
footer->setAutoFillBackground(true);
|
||||
{
|
||||
QPalette fp = footer->palette();
|
||||
fp.setColor(QPalette::Window, qApp->palette().color(QPalette::Window).darker(104));
|
||||
footer->setPalette(fp);
|
||||
}
|
||||
auto *footerLayout = new QHBoxLayout(footer);
|
||||
footerLayout->setContentsMargins(12, 8, 12, 8);
|
||||
|
||||
revertButton = new QPushButton(this);
|
||||
|
||||
buttonBox = new QDialogButtonBox;
|
||||
resetBtn = buttonBox->addButton(tr("Reset"), QDialogButtonBox::ResetRole);
|
||||
applyBtn = buttonBox->addButton(tr("Apply"), QDialogButtonBox::ApplyRole);
|
||||
saveBtn = buttonBox->addButton(tr("Save && Apply"), QDialogButtonBox::AcceptRole);
|
||||
closeBtn = buttonBox->addButton(QDialogButtonBox::Close);
|
||||
|
||||
footerLayout->addWidget(revertButton);
|
||||
footerLayout->addStretch();
|
||||
footerLayout->addWidget(buttonBox);
|
||||
root->addWidget(footer);
|
||||
|
||||
// Connections
|
||||
connect(schemeComboBox, &QComboBox::currentTextChanged, this, &PaletteEditorDialog::onSchemeChanged);
|
||||
connect(quickSetupPanel, &QuickSetupPanel::generateRequested, this, &PaletteEditorDialog::onGenerateFromAccent);
|
||||
connect(revertButton, &QPushButton::clicked, this, &PaletteEditorDialog::onRevertToDefault);
|
||||
connect(resetBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onReset);
|
||||
connect(applyBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onApply);
|
||||
connect(saveBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onSave);
|
||||
connect(closeBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||||
|
||||
connect(paletteGridToggleButton, &QPushButton::toggled, this, [this](bool open) {
|
||||
paletteGridToggleButton->setText(open ? tr("▼ Edit Palette") : tr("▶ Edit Palette"));
|
||||
paletteGridSeparator->setVisible(open);
|
||||
paletteGrid->setVisible(open);
|
||||
|
||||
if (open) {
|
||||
setMinimumHeight(680);
|
||||
resize(width(), 680);
|
||||
} else {
|
||||
setMinimumHeight(220);
|
||||
adjustSize(); // shrinks to fit just the visible content
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Palette Editor — %1").arg(themeName));
|
||||
titleLabel->setText(tr("<b>Palette Editor</b> · %1").arg(themeName));
|
||||
|
||||
// Revert button only makes sense when the theme ships default palette files
|
||||
const bool hasDefault = PaletteConfig::fromDefault(themeDirPath, "Light").hasPalette() ||
|
||||
PaletteConfig::fromDefault(themeDirPath, "Dark").hasPalette();
|
||||
revertButton->setEnabled(hasDefault);
|
||||
if (!hasDefault) {
|
||||
revertButton->setToolTip(tr("This theme ships no default palette files"));
|
||||
} else {
|
||||
revertButton->setToolTip(tr("Replace current colours with the theme author's defaults"));
|
||||
}
|
||||
|
||||
schemeComboBox->setToolTip(tr("Switch between the light and dark palette files"));
|
||||
editingLabel->setText(tr("Editing:"));
|
||||
paletteGridToggleButton->setText(tr("▶ Edit Palette"));
|
||||
paletteGridToggleButton->setToolTip(tr("Show or hide the per-role colour grid for manual tweaks"));
|
||||
revertButton->setText(tr("↺ Revert to theme default"));
|
||||
|
||||
resetBtn->setText(tr("Reset"));
|
||||
applyBtn->setText(tr("Apply"));
|
||||
saveBtn->setText(tr("Save && Apply"));
|
||||
resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette"));
|
||||
applyBtn->setToolTip(tr("Preview this palette without saving to disk"));
|
||||
saveBtn->setToolTip(tr("Write palette-%1.toml and reload the theme").arg(loadedScheme.toLower()));
|
||||
|
||||
if (themeDirPath.isEmpty()) {
|
||||
saveBtn->setEnabled(false);
|
||||
saveBtn->setToolTip(tr("Cannot save: this theme has no directory on disk"));
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::loadSchemes()
|
||||
{
|
||||
const QStringList schemes = {"Light", "Dark"};
|
||||
for (const QString &scheme : schemes) {
|
||||
PaletteConfig cfg = PaletteConfig::fromScheme(themeDirPath, scheme);
|
||||
|
||||
if (!cfg.hasPalette()) {
|
||||
cfg = PaletteConfig::fromDefault(themeDirPath, scheme);
|
||||
}
|
||||
|
||||
if (!cfg.hasPalette()) {
|
||||
const QPalette appPal = qApp->palette();
|
||||
for (auto group : {QPalette::Active, QPalette::Disabled, QPalette::Inactive}) {
|
||||
for (int i = 0; i < QPalette::NColorRoles; ++i) {
|
||||
auto role = static_cast<QPalette::ColorRole>(i);
|
||||
if (role != QPalette::NoRole) {
|
||||
cfg.colors[group][role] = appPal.color(group, role);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
savedConfig[scheme] = cfg;
|
||||
workingConfig[scheme] = cfg;
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::seedAccentFromScheme(const QString &scheme)
|
||||
{
|
||||
QColor seed = workingConfig.value(scheme).colors.value(QPalette::Active).value(QPalette::Highlight);
|
||||
if (seed.isValid()) {
|
||||
quickSetupPanel->setAccentColor(seed);
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onSchemeChanged(const QString &scheme)
|
||||
{
|
||||
// Snapshot unsaved edits for the scheme we're leaving
|
||||
if (!loadedScheme.isEmpty()) {
|
||||
workingConfig[loadedScheme] = paletteGrid->currentPaletteConfig();
|
||||
}
|
||||
|
||||
loadedScheme = scheme;
|
||||
paletteGrid->loadPalette(workingConfig.value(scheme));
|
||||
seedAccentFromScheme(scheme);
|
||||
onApply();
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onGenerateFromAccent(const QColor &accent, int intensity)
|
||||
{
|
||||
PaletteConfig cfg = PaletteGenerator::fromAccent(accent, intensity, loadedScheme);
|
||||
workingConfig[loadedScheme] = cfg;
|
||||
paletteGrid->loadPalette(cfg);
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onApply()
|
||||
{
|
||||
themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme);
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onSave()
|
||||
{
|
||||
if (loadedScheme.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PaletteConfig cfg = paletteGrid->currentPaletteConfig();
|
||||
|
||||
if (!ThemeManager::savePaletteConfig(themeDirPath, loadedScheme, cfg)) {
|
||||
QMessageBox::warning(this, tr("Save failed"),
|
||||
tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(loadedScheme), themeDirPath));
|
||||
return;
|
||||
}
|
||||
|
||||
ThemeConfig globalCfg = ThemeConfig::fromThemeDir(themeDirPath);
|
||||
globalCfg.colorScheme = loadedScheme;
|
||||
globalCfg.save(themeDirPath);
|
||||
|
||||
savedConfig[loadedScheme] = cfg;
|
||||
workingConfig[loadedScheme] = cfg;
|
||||
themeManager->reloadCurrentTheme();
|
||||
accept();
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onReset()
|
||||
{
|
||||
workingConfig[loadedScheme] = savedConfig[loadedScheme];
|
||||
paletteGrid->loadPalette(savedConfig[loadedScheme]);
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onRevertToDefault()
|
||||
{
|
||||
PaletteConfig def = PaletteConfig::fromDefault(themeDirPath, loadedScheme);
|
||||
if (!def.hasPalette()) {
|
||||
QMessageBox::information(this, tr("No default found"),
|
||||
tr("No default palette file found for the \"%1\" scheme.").arg(loadedScheme));
|
||||
return;
|
||||
}
|
||||
workingConfig[loadedScheme] = def;
|
||||
paletteGrid->loadPalette(def);
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::changeEvent(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::PaletteChange) {
|
||||
QTimer::singleShot(0, this, &PaletteEditorDialog::refreshChromePalettes);
|
||||
}
|
||||
|
||||
QDialog::changeEvent(e);
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::refreshChromePalettes()
|
||||
{
|
||||
const QPalette base = qApp->palette();
|
||||
|
||||
if (header) {
|
||||
QPalette hp = header->palette();
|
||||
hp.setColor(QPalette::Window, base.color(QPalette::Window).darker(108));
|
||||
header->setPalette(hp);
|
||||
header->update();
|
||||
}
|
||||
if (footer) {
|
||||
QPalette fp = footer->palette();
|
||||
fp.setColor(QPalette::Window, base.color(QPalette::Window).darker(104));
|
||||
footer->setPalette(fp);
|
||||
footer->update();
|
||||
}
|
||||
if (quickSetupPanel) {
|
||||
QPalette sp = quickSetupPanel->palette();
|
||||
sp.setColor(QPalette::Window, base.color(QPalette::Window).darker(102));
|
||||
quickSetupPanel->setPalette(sp);
|
||||
quickSetupPanel->update();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#ifndef COCKATRICE_PALETTE_EDITOR_DIALOG_H
|
||||
#define COCKATRICE_PALETTE_EDITOR_DIALOG_H
|
||||
|
||||
#include "../theme_config.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QFrame>
|
||||
#include <QMap>
|
||||
|
||||
class QLabel;
|
||||
class QComboBox;
|
||||
class QDialogButtonBox;
|
||||
class QPushButton;
|
||||
class PaletteGridWidget;
|
||||
class QuickSetupPanel;
|
||||
|
||||
class PaletteEditorDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PaletteEditorDialog(const QString &themeDirPath, const QString &themeName, QWidget *parent = nullptr);
|
||||
void loadSchemes();
|
||||
|
||||
private slots:
|
||||
void onSave();
|
||||
void onApply();
|
||||
void onReset();
|
||||
void onRevertToDefault();
|
||||
void onSchemeChanged(const QString &scheme);
|
||||
void onGenerateFromAccent(const QColor &accent, int intensity);
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void retranslateUi();
|
||||
void refreshChromePalettes();
|
||||
void loadScheme(const QString &scheme); // snapshot current, switch, load
|
||||
void seedAccentFromScheme(const QString &scheme);
|
||||
|
||||
// Sub-widgets
|
||||
QWidget *header;
|
||||
QLabel *titleLabel;
|
||||
QLabel *editingLabel;
|
||||
QuickSetupPanel *quickSetupPanel = nullptr;
|
||||
PaletteGridWidget *paletteGrid = nullptr;
|
||||
QPushButton *paletteGridToggleButton = nullptr;
|
||||
QFrame *paletteGridSeparator = nullptr;
|
||||
QWidget *footer;
|
||||
QComboBox *schemeComboBox = nullptr;
|
||||
QDialogButtonBox *buttonBox = nullptr;
|
||||
QPushButton *resetBtn = nullptr;
|
||||
QPushButton *applyBtn = nullptr;
|
||||
QPushButton *saveBtn = nullptr;
|
||||
QPushButton *closeBtn = nullptr;
|
||||
QPushButton *revertButton = nullptr;
|
||||
|
||||
// State
|
||||
QString themeDirPath;
|
||||
QString themeName;
|
||||
QString loadedScheme;
|
||||
|
||||
QMap<QString, PaletteConfig> workingConfig;
|
||||
QMap<QString, PaletteConfig> savedConfig;
|
||||
|
||||
protected:
|
||||
void changeEvent(QEvent *e) override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H
|
||||
200
cockatrice/src/interface/palette_editor/palette_generator.cpp
Normal file
200
cockatrice/src/interface/palette_editor/palette_generator.cpp
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
#include "palette_generator.h"
|
||||
|
||||
#include <QColor>
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// PaletteGenerator::fromAccent
|
||||
//
|
||||
// Three intensity bands:
|
||||
// 0–30 Subtle — Highlight / links / BrightText take on the hue;
|
||||
// backgrounds stay neutral grey.
|
||||
// 30–70 Accented — Button, ToolTipBase, AlternateBase, shading tint in;
|
||||
// backgrounds get a faint hue wash.
|
||||
// 70–100 Chromatic— Window and Base pick up real saturation; the whole
|
||||
// application reads as that colour, text stays readable.
|
||||
//
|
||||
// Key principles:
|
||||
// • Quadratic saturation curve: low end feels truly subtle, top is vivid.
|
||||
// • Each role has its own saturation ceiling; buttons always pop above window.
|
||||
// • Base stays near-white / near-black regardless of intensity for legibility.
|
||||
// • Button text contrast is computed from the real button color, not assumed.
|
||||
// • Tooltip blends from classic yellow to hue-tinted above intensity ~25.
|
||||
// • 3D shading ladder (Light→Shadow) carries the same hue for coherence.
|
||||
// • Disabled: text → mid-gray, backgrounds → match Window.
|
||||
// • Inactive Highlight fades to a near-bg tone so it doesn't compete.
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
namespace PaletteGenerator
|
||||
{
|
||||
|
||||
PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &scheme)
|
||||
{
|
||||
PaletteConfig cfg;
|
||||
const bool dark = scheme.compare("Dark", Qt::CaseInsensitive) == 0;
|
||||
const double t = intensity / 100.0; // 0.0 – 1.0
|
||||
|
||||
int h = accent.hslHue();
|
||||
const bool achromatic = (h < 0);
|
||||
if (achromatic) {
|
||||
h = 0;
|
||||
}
|
||||
|
||||
// Saturation budgets
|
||||
// Quadratic ease-in means the subtle end is genuinely subtle and the
|
||||
// full end is bold without being garish.
|
||||
auto sat = [&](double maxSat) -> int {
|
||||
if (achromatic) {
|
||||
return 0;
|
||||
}
|
||||
return qRound(maxSat * t * t);
|
||||
};
|
||||
|
||||
const int satWindow = sat(dark ? 80.0 : 90.0);
|
||||
const int satBase = sat(dark ? 25.0 : 20.0); // text areas stay near-white/black
|
||||
const int satAlt = sat(dark ? 90.0 : 100.0);
|
||||
const int satButton = sat(dark ? 120.0 : 130.0); // buttons pop above the bg
|
||||
const int satTooltip = sat(dark ? 90.0 : 80.0);
|
||||
const int satHighlight = achromatic ? 0 : qRound(accent.hslSaturation() * (0.45 + 0.55 * t));
|
||||
const int satShadeHi = sat(dark ? 60.0 : 50.0); // Light / Midlight
|
||||
const int satShadeLo = sat(dark ? 90.0 : 70.0); // Mid / Dark
|
||||
|
||||
// Per-role lightness
|
||||
// Nudge lightness slightly as saturation rises to compensate for the
|
||||
// Helmholtz-Kohlrausch effect (saturated colors look lighter/heavier).
|
||||
const int winL = dark ? (28 + qRound(t * 8)) : (242 - qRound(t * 6));
|
||||
const int baseL = dark ? (43 + qRound(t * 6)) : 252;
|
||||
const int altL = dark ? (36 + qRound(t * 9)) : (234 - qRound(t * 5));
|
||||
const int btnL = dark ? (56 + qRound(t * 10)) : (230 - qRound(t * 10));
|
||||
const int tipL = dark ? (52 + qRound(t * 8)) : (248 - qRound(t * 6));
|
||||
|
||||
// Highlight color
|
||||
QColor hl;
|
||||
if (achromatic) {
|
||||
hl = dark ? QColor(105, 105, 105) : QColor(95, 95, 95);
|
||||
} else if (dark) {
|
||||
int L = qBound(105, accent.lightness() + qRound(45.0 * (1.0 - t)), 215);
|
||||
hl = QColor::fromHsl(h, qMin(255, satHighlight), L);
|
||||
} else {
|
||||
int L = qBound(50, accent.lightness() - qRound(25.0 * t), 180);
|
||||
hl = QColor::fromHsl(h, qMin(255, satHighlight), L);
|
||||
}
|
||||
const double hlLuma = 0.299 * hl.red() + 0.587 * hl.green() + 0.114 * hl.blue();
|
||||
const QColor hlText = (hlLuma > 135) ? Qt::black : Qt::white;
|
||||
|
||||
// Local helpers
|
||||
using CR = QPalette::ColorRole;
|
||||
using CG = QPalette::ColorGroup;
|
||||
|
||||
auto hsl = [&](int lightness, int s) -> QColor {
|
||||
return QColor::fromHsl(h, qBound(0, s, 255), qBound(0, lightness, 255));
|
||||
};
|
||||
|
||||
auto textOn = [](const QColor &bg) -> QColor {
|
||||
double luma = 0.299 * bg.red() + 0.587 * bg.green() + 0.114 * bg.blue();
|
||||
return (luma > 135) ? Qt::black : Qt::white;
|
||||
};
|
||||
|
||||
auto set3 = [&](CR role, QColor active, QColor disabled, QColor inactive) {
|
||||
cfg.colors[CG::Active][role] = active;
|
||||
cfg.colors[CG::Disabled][role] = disabled;
|
||||
cfg.colors[CG::Inactive][role] = inactive;
|
||||
};
|
||||
|
||||
auto setAll = [&](CR role, QColor c) { set3(role, c, c, c); };
|
||||
|
||||
// Tooltip: blend classic yellow → hue-tinted above t≈0.20
|
||||
QColor bg_tip;
|
||||
if (achromatic || t < 0.20) {
|
||||
bg_tip = QColor(255, 255, 220);
|
||||
} else {
|
||||
QColor tinted = hsl(tipL, satTooltip);
|
||||
double blend = qMin(1.0, (t - 0.20) / 0.55);
|
||||
QColor yellow(255, 255, 220);
|
||||
bg_tip = QColor(qRound(yellow.red() * (1.0 - blend) + tinted.red() * blend),
|
||||
qRound(yellow.green() * (1.0 - blend) + tinted.green() * blend),
|
||||
qRound(yellow.blue() * (1.0 - blend) + tinted.blue() * blend));
|
||||
}
|
||||
|
||||
// Backgrounds
|
||||
const QColor bg_win = hsl(winL, satWindow);
|
||||
const QColor bg_base = hsl(baseL, satBase);
|
||||
const QColor bg_alt = hsl(altL, satAlt);
|
||||
const QColor bg_btn = hsl(btnL, satButton);
|
||||
|
||||
set3(CR::Window, bg_win, bg_win, bg_win);
|
||||
set3(CR::Base, bg_base, bg_win, bg_base);
|
||||
set3(CR::AlternateBase, bg_alt, bg_alt, bg_alt);
|
||||
set3(CR::Button, bg_btn, bg_win, bg_btn);
|
||||
set3(CR::ToolTipBase, bg_tip, bg_tip, bg_tip);
|
||||
|
||||
// Foreground text
|
||||
const QColor winText = dark ? Qt::white : Qt::black;
|
||||
const QColor disText = dark ? QColor(157, 157, 157) : QColor(120, 120, 120);
|
||||
const QColor disBtnText = dark ? QColor(120, 120, 120) : QColor(150, 150, 150);
|
||||
|
||||
set3(CR::WindowText, winText, disText, winText);
|
||||
set3(CR::Text, winText, disText, winText);
|
||||
set3(CR::ButtonText, textOn(bg_btn), disBtnText, textOn(bg_btn));
|
||||
setAll(CR::ToolTipText, textOn(bg_tip));
|
||||
|
||||
const QColor phAlpha = dark ? QColor(255, 255, 255, 110) : QColor(0, 0, 0, 110);
|
||||
const QColor phDis = dark ? QColor(255, 255, 255, 70) : QColor(0, 0, 0, 70);
|
||||
set3(CR::PlaceholderText, phAlpha, phDis, phAlpha);
|
||||
|
||||
// Highlight / selection
|
||||
const QColor inactiveHl = hsl(winL + (dark ? 14 : -10), satWindow);
|
||||
cfg.colors[CG::Active][CR::Highlight] = hl;
|
||||
cfg.colors[CG::Disabled][CR::Highlight] = inactiveHl;
|
||||
cfg.colors[CG::Inactive][CR::Highlight] = inactiveHl;
|
||||
cfg.colors[CG::Active][CR::HighlightedText] = hlText;
|
||||
cfg.colors[CG::Disabled][CR::HighlightedText] = disText;
|
||||
cfg.colors[CG::Inactive][CR::HighlightedText] = dark ? Qt::white : Qt::black;
|
||||
|
||||
// BrightText
|
||||
QColor bright;
|
||||
if (achromatic) {
|
||||
bright = dark ? Qt::white : Qt::black;
|
||||
} else if (dark) {
|
||||
bright = QColor::fromHsl(h, qMin(255, satHighlight + 25), qMin(235, hl.lightness() + 50));
|
||||
} else {
|
||||
bright = Qt::white;
|
||||
}
|
||||
setAll(CR::BrightText, bright);
|
||||
|
||||
// Links
|
||||
QColor link, linkV;
|
||||
if (achromatic) {
|
||||
link = dark ? QColor(100, 200, 255) : QColor(0, 0, 210);
|
||||
linkV = dark ? QColor(200, 100, 255) : QColor(128, 0, 180);
|
||||
} else if (dark) {
|
||||
link = QColor::fromHsl(h, qMin(255, satHighlight), qMin(230, hl.lightness() + 75));
|
||||
linkV = QColor::fromHsl((h + 30) % 360, qMin(255, satHighlight), qMin(215, hl.lightness() + 55));
|
||||
} else {
|
||||
link = QColor::fromHsl(h, qMin(255, satHighlight), qMax(40, hl.lightness() - 75));
|
||||
linkV = QColor::fromHsl((h + 30) % 360, qMin(255, satHighlight), qMax(30, hl.lightness() - 95));
|
||||
}
|
||||
set3(CR::Link, link, dark ? QColor(48, 140, 198) : QColor(0, 0, 255), link);
|
||||
set3(CR::LinkVisited, linkV, dark ? QColor(180, 80, 255) : QColor(255, 0, 255), linkV);
|
||||
|
||||
// 3D / frame shading
|
||||
if (dark) {
|
||||
setAll(CR::Light, hsl(115, qMin(255, satShadeHi)));
|
||||
setAll(CR::Midlight, hsl(82, qMin(255, satShadeHi)));
|
||||
setAll(CR::Mid, hsl(37, satShadeLo));
|
||||
setAll(CR::Dark, hsl(22, satShadeLo));
|
||||
setAll(CR::Shadow, Qt::black);
|
||||
} else {
|
||||
setAll(CR::Light, Qt::white);
|
||||
setAll(CR::Midlight, hsl(226, qMin(255, satShadeHi)));
|
||||
setAll(CR::Mid, hsl(158, satShadeLo));
|
||||
setAll(CR::Dark, hsl(148, satShadeLo));
|
||||
// Shadow stays neutral — tinting it makes the UI look bruised
|
||||
cfg.colors[CG::Active][CR::Shadow] = QColor(105, 105, 105);
|
||||
cfg.colors[CG::Disabled][CR::Shadow] = Qt::black;
|
||||
cfg.colors[CG::Inactive][CR::Shadow] = QColor(105, 105, 105);
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
} // namespace PaletteGenerator
|
||||
16
cockatrice/src/interface/palette_editor/palette_generator.h
Normal file
16
cockatrice/src/interface/palette_editor/palette_generator.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef COCKATRICE_PALETTE_GENERATOR_H
|
||||
#define COCKATRICE_PALETTE_GENERATOR_H
|
||||
|
||||
#include "../theme_config.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QString>
|
||||
|
||||
// All QPalette roles are derived from a single accent color and an
|
||||
// intensity value (0-100). See the .cpp for the full band breakdown.
|
||||
namespace PaletteGenerator
|
||||
{
|
||||
PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &scheme);
|
||||
} // namespace PaletteGenerator
|
||||
|
||||
#endif // COCKATRICE_PALETTE_GENERATOR_H
|
||||
179
cockatrice/src/interface/palette_editor/palette_grid_widget.cpp
Normal file
179
cockatrice/src/interface/palette_editor/palette_grid_widget.cpp
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#include "palette_grid_widget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QMetaEnum>
|
||||
#include <QScrollArea>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
static QList<QPalette::ColorRole> allRoles()
|
||||
{
|
||||
QList<QPalette::ColorRole> roles;
|
||||
for (int i = 0; i < QPalette::NColorRoles; ++i) {
|
||||
auto r = static_cast<QPalette::ColorRole>(i);
|
||||
if (r != QPalette::NoRole) {
|
||||
roles << r;
|
||||
}
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
static const QList<QPalette::ColorGroup> ALL_GROUPS = {QPalette::Active, QPalette::Disabled, QPalette::Inactive};
|
||||
|
||||
static const QMap<QPalette::ColorRole, const char *> ROLE_DESCRIPTIONS = {
|
||||
{QPalette::Window, QT_TR_NOOP("Main window / dialog background")},
|
||||
{QPalette::WindowText, QT_TR_NOOP("Text drawn on Window")},
|
||||
{QPalette::Base, QT_TR_NOOP("Background for text input widgets")},
|
||||
{QPalette::Text, QT_TR_NOOP("Text in input widgets")},
|
||||
{QPalette::Button, QT_TR_NOOP("Button background")},
|
||||
{QPalette::ButtonText, QT_TR_NOOP("Button label text")},
|
||||
{QPalette::BrightText, QT_TR_NOOP("High-contrast text (e.g. checked items)")},
|
||||
{QPalette::Highlight, QT_TR_NOOP("Selection / focus highlight")},
|
||||
{QPalette::HighlightedText, QT_TR_NOOP("Text on top of Highlight")},
|
||||
{QPalette::Link, QT_TR_NOOP("Unvisited hyperlink")},
|
||||
{QPalette::LinkVisited, QT_TR_NOOP("Visited hyperlink")},
|
||||
{QPalette::AlternateBase, QT_TR_NOOP("Alternating row background in views")},
|
||||
{QPalette::ToolTipBase, QT_TR_NOOP("Tooltip background")},
|
||||
{QPalette::ToolTipText, QT_TR_NOOP("Tooltip text")},
|
||||
{QPalette::PlaceholderText, QT_TR_NOOP("Placeholder / hint text in inputs")},
|
||||
{QPalette::Light, QT_TR_NOOP("Lighter than Button (3-D highlight)")},
|
||||
{QPalette::Midlight, QT_TR_NOOP("Between Button and Light")},
|
||||
{QPalette::Mid, QT_TR_NOOP("Between Button and Dark")},
|
||||
{QPalette::Dark, QT_TR_NOOP("Darker than Button (3-D shadow)")},
|
||||
{QPalette::Shadow, QT_TR_NOOP("Very dark shadow colour")},
|
||||
};
|
||||
|
||||
PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
scroll = new QScrollArea(this);
|
||||
scroll->setWidgetResizable(true);
|
||||
scroll->setFrameShape(QFrame::NoFrame);
|
||||
|
||||
gridHost = new QWidget;
|
||||
buildGrid(gridHost);
|
||||
refreshChromePalettes();
|
||||
scroll->setWidget(gridHost);
|
||||
|
||||
layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(scroll);
|
||||
}
|
||||
|
||||
void PaletteGridWidget::buildGrid(QWidget *host)
|
||||
{
|
||||
QMetaEnum roleEnum = QMetaEnum::fromType<QPalette::ColorRole>();
|
||||
|
||||
auto *grid = new QGridLayout(host);
|
||||
grid->setSpacing(3);
|
||||
grid->setContentsMargins(12, 8, 12, 8);
|
||||
grid->setColumnStretch(0, 1);
|
||||
grid->setColumnStretch(1, 0);
|
||||
grid->setColumnStretch(2, 0);
|
||||
grid->setColumnStretch(3, 0);
|
||||
|
||||
// Column headers
|
||||
const QStringList groupHeaders = {tr("Active"), tr("Disabled"), tr("Inactive")};
|
||||
const QStringList groupTips = {
|
||||
tr("Normal interactive state"),
|
||||
tr("Widget is disabled / not interactive"),
|
||||
tr("Window is in background / unfocused"),
|
||||
};
|
||||
for (int col = 0; col < 3; ++col) {
|
||||
auto *label = new QLabel(groupHeaders[col], host);
|
||||
label->setAlignment(Qt::AlignCenter);
|
||||
label->setToolTip(groupTips[col]);
|
||||
QFont f = label->font();
|
||||
f.setBold(true);
|
||||
label->setFont(f);
|
||||
label->setAutoFillBackground(true);
|
||||
label->setContentsMargins(4, 4, 4, 4);
|
||||
grid->addWidget(label, 0, col + 1);
|
||||
headerLabels.push_back(label);
|
||||
}
|
||||
|
||||
// Role rows
|
||||
const auto roles = allRoles();
|
||||
for (int row = 0; row < roles.size(); ++row) {
|
||||
auto role = roles[row];
|
||||
const char *name = roleEnum.valueToKey(role);
|
||||
|
||||
// Alternating row shade
|
||||
if (row % 2 == 0) {
|
||||
for (int col = 0; col < 4; ++col) {
|
||||
auto *shade = new QWidget(host);
|
||||
shade->setAutoFillBackground(true);
|
||||
grid->addWidget(shade, row + 1, col);
|
||||
rowShadeWidgets.push_back(shade);
|
||||
}
|
||||
}
|
||||
|
||||
auto *label = new QLabel(QString(name), host);
|
||||
label->setToolTip(ROLE_DESCRIPTIONS.value(role, {}));
|
||||
label->setContentsMargins(4, 2, 8, 2);
|
||||
grid->addWidget(label, row + 1, 0);
|
||||
|
||||
for (int col = 0; col < 3; ++col) {
|
||||
auto group = ALL_GROUPS[col];
|
||||
auto *btn = new ColorButton(host);
|
||||
colorButtons[group][role] = btn;
|
||||
grid->addWidget(btn, row + 1, col + 1, Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteGridWidget::changeEvent(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::PaletteChange) {
|
||||
QTimer::singleShot(0, this, &PaletteGridWidget::refreshChromePalettes);
|
||||
}
|
||||
|
||||
QWidget::changeEvent(e);
|
||||
}
|
||||
|
||||
void PaletteGridWidget::refreshChromePalettes()
|
||||
{
|
||||
const QPalette base = qApp->palette();
|
||||
const QColor alt = base.color(QPalette::AlternateBase);
|
||||
|
||||
// Header labels
|
||||
for (auto *label : headerLabels) {
|
||||
QPalette lp = label->palette();
|
||||
lp.setColor(QPalette::Window, alt);
|
||||
label->setPalette(lp);
|
||||
label->update();
|
||||
}
|
||||
|
||||
// Alternating row backgrounds
|
||||
for (auto *shade : rowShadeWidgets) {
|
||||
QPalette sp = shade->palette();
|
||||
sp.setColor(QPalette::Window, alt);
|
||||
shade->setPalette(sp);
|
||||
shade->update();
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteGridWidget::loadPalette(const PaletteConfig &cfg)
|
||||
{
|
||||
for (auto group : ALL_GROUPS) {
|
||||
for (auto role : allRoles()) {
|
||||
QColor color = cfg.colors.value(group).value(role);
|
||||
if (!color.isValid()) {
|
||||
color = qApp->palette().color(group, role);
|
||||
}
|
||||
colorButtons[group][role]->setColor(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PaletteConfig PaletteGridWidget::currentPaletteConfig() const
|
||||
{
|
||||
PaletteConfig cfg;
|
||||
for (auto group : ALL_GROUPS) {
|
||||
for (auto role : allRoles()) {
|
||||
cfg.colors[group][role] = colorButtons[group][role]->getColor();
|
||||
}
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef COCKATRICE_PALETTE_GRID_WIDGET_H
|
||||
#define COCKATRICE_PALETTE_GRID_WIDGET_H
|
||||
|
||||
#include "../theme_config.h"
|
||||
#include "color_button.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QPalette>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class QLabel;
|
||||
class QScrollArea;
|
||||
// Scrollable grid of ColorButtons — one per (ColorGroup × ColorRole) cell.
|
||||
// Owns the load/read round-trip for PaletteConfig but has no file I/O itself.
|
||||
class PaletteGridWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PaletteGridWidget(QWidget *parent = nullptr);
|
||||
|
||||
void loadPalette(const PaletteConfig &cfg);
|
||||
PaletteConfig currentPaletteConfig() const;
|
||||
|
||||
private:
|
||||
void buildGrid(QWidget *host);
|
||||
void changeEvent(QEvent *e);
|
||||
void refreshChromePalettes();
|
||||
|
||||
QMap<QPalette::ColorGroup, QMap<QPalette::ColorRole, ColorButton *>> colorButtons;
|
||||
QScrollArea *scroll;
|
||||
QWidget *gridHost;
|
||||
QVBoxLayout *layout;
|
||||
QVector<QLabel *> headerLabels;
|
||||
QVector<QWidget *> rowShadeWidgets;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PALETTE_GRID_WIDGET_H
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#include "quick_setup_panel.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
|
||||
QuickSetupPanel::QuickSetupPanel(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(12, 8, 12, 8);
|
||||
layout->setSpacing(10);
|
||||
|
||||
heading = new QLabel(this);
|
||||
heading->setTextFormat(Qt::RichText);
|
||||
|
||||
accentLabel = new QLabel(this);
|
||||
accentButton = new ColorButton(this);
|
||||
accentButton->setColor(QColor(20, 140, 60));
|
||||
|
||||
intensityLabel = new QLabel(this);
|
||||
|
||||
labelLow = new QLabel(this);
|
||||
labelHigh = new QLabel(this);
|
||||
QFont small = labelLow->font();
|
||||
small.setPointSizeF(small.pointSizeF() * 0.82);
|
||||
labelLow->setFont(small);
|
||||
labelHigh->setFont(small);
|
||||
QPalette dimmed = labelLow->palette();
|
||||
dimmed.setColor(QPalette::WindowText, qApp->palette().color(QPalette::Mid));
|
||||
labelLow->setPalette(dimmed);
|
||||
labelHigh->setPalette(dimmed);
|
||||
|
||||
intensitySlider = new QSlider(Qt::Horizontal, this);
|
||||
intensitySlider->setRange(0, 100);
|
||||
intensitySlider->setValue(70);
|
||||
intensitySlider->setFixedWidth(160);
|
||||
|
||||
intensityPercentageLabel = new QLabel(this);
|
||||
intensityPercentageLabel->setFixedWidth(34);
|
||||
intensityPercentageLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
|
||||
generateButton = new QPushButton(this);
|
||||
|
||||
layout->addWidget(heading);
|
||||
layout->addSpacing(6);
|
||||
layout->addWidget(accentLabel);
|
||||
layout->addWidget(accentButton);
|
||||
layout->addSpacing(12);
|
||||
layout->addWidget(intensityLabel);
|
||||
layout->addWidget(labelLow);
|
||||
layout->addWidget(intensitySlider);
|
||||
layout->addWidget(labelHigh);
|
||||
layout->addWidget(intensityPercentageLabel);
|
||||
layout->addStretch();
|
||||
layout->addWidget(generateButton);
|
||||
|
||||
connect(intensitySlider, &QSlider::valueChanged, this,
|
||||
[this](int v) { intensityPercentageLabel->setText(tr("%1%").arg(v)); });
|
||||
connect(generateButton, &QPushButton::clicked, this,
|
||||
[this] { emit generateRequested(accentButton->getColor(), intensitySlider->value()); });
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void QuickSetupPanel::retranslateUi()
|
||||
{
|
||||
heading->setText(tr("<b>Quick Setup</b>"));
|
||||
heading->setToolTip(tr("Generate all palette roles automatically from a single accent colour"));
|
||||
accentLabel->setText(tr("Accent:"));
|
||||
accentButton->setToolTip(tr("Primary hue. Used directly for highlights and links.\n"
|
||||
"At high intensity it also tints buttons and backgrounds."));
|
||||
intensityLabel->setText(tr("Intensity:"));
|
||||
labelLow->setText(tr("Subtle"));
|
||||
labelHigh->setText(tr("Full colour"));
|
||||
intensitySlider->setToolTip(tr("0–30 Subtle tint — only highlights and links change hue\n"
|
||||
"30–70 Accented — buttons, tooltips, and borders join in\n"
|
||||
"70–100 Full colour — backgrounds, everything"));
|
||||
intensityPercentageLabel->setText(tr("70%"));
|
||||
|
||||
generateButton->setText(tr("Generate ↓"));
|
||||
generateButton->setToolTip(tr("Derive all palette roles from the accent colour above.\n"
|
||||
"Fine-tune individual colours in the grid afterwards."));
|
||||
}
|
||||
|
||||
QColor QuickSetupPanel::accentColor() const
|
||||
{
|
||||
return accentButton->getColor();
|
||||
}
|
||||
|
||||
int QuickSetupPanel::intensity() const
|
||||
{
|
||||
return intensitySlider->value();
|
||||
}
|
||||
|
||||
void QuickSetupPanel::setAccentColor(const QColor &c)
|
||||
{
|
||||
accentButton->setColor(c);
|
||||
}
|
||||
94
cockatrice/src/interface/palette_editor/quick_setup_panel.h
Normal file
94
cockatrice/src/interface/palette_editor/quick_setup_panel.h
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#ifndef COCKATRICE_QUICK_SETUP_PANEL_H
|
||||
#define COCKATRICE_QUICK_SETUP_PANEL_H
|
||||
|
||||
#include "color_button.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QPushButton;
|
||||
class QHBoxLayout;
|
||||
class QLabel;
|
||||
class QSlider;
|
||||
|
||||
/**
|
||||
* @class QuickSetupPanel
|
||||
* @brief Provides a compact "Quick Setup" interface for generating theme palettes.
|
||||
*
|
||||
* The panel contains:
|
||||
* - an accent color picker,
|
||||
* - an intensity slider,
|
||||
* - and a generate button.
|
||||
*
|
||||
* When the user clicks the generate button, the panel emits
|
||||
* generateRequested() with the currently selected accent color
|
||||
* and intensity value.
|
||||
*
|
||||
* Typically used together with PaletteGenerator::fromAccent()
|
||||
* to quickly generate color schemes from a chosen accent color.
|
||||
*/
|
||||
class QuickSetupPanel : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs the quick setup panel.
|
||||
*
|
||||
* @param parent Optional parent widget.
|
||||
*/
|
||||
explicit QuickSetupPanel(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Retranslates all user-visible strings.
|
||||
*
|
||||
* Intended to be called when the application language changes.
|
||||
*/
|
||||
void retranslateUi();
|
||||
|
||||
/**
|
||||
* @brief Returns the currently selected accent color.
|
||||
*
|
||||
* @return The selected accent color.
|
||||
*/
|
||||
QColor accentColor() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the current intensity slider value.
|
||||
*
|
||||
* @return The selected intensity value.
|
||||
*/
|
||||
int intensity() const;
|
||||
|
||||
/**
|
||||
* @brief Updates the displayed accent color.
|
||||
*
|
||||
* Used by the parent dialog when switching schemes to keep
|
||||
* the color swatch synchronized with the active palette.
|
||||
*
|
||||
* @param c The new accent color.
|
||||
*/
|
||||
void setAccentColor(const QColor &c);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Emitted when the user requests palette generation.
|
||||
*
|
||||
* @param accent The selected accent color.
|
||||
* @param intensity The selected intensity value.
|
||||
*/
|
||||
void generateRequested(QColor accent, int intensity);
|
||||
|
||||
private:
|
||||
QHBoxLayout *layout;
|
||||
QLabel *heading;
|
||||
QLabel *accentLabel;
|
||||
ColorButton *accentButton;
|
||||
QLabel *intensityLabel;
|
||||
QLabel *labelLow;
|
||||
QLabel *labelHigh;
|
||||
QSlider *intensitySlider;
|
||||
QLabel *intensityPercentageLabel;
|
||||
QPushButton *generateButton;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_QUICK_SETUP_PANEL_H
|
||||
|
|
@ -77,8 +77,9 @@ QMap<QString, QPixmap> PhasePixmapGenerator::pmCache;
|
|||
QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name)
|
||||
{
|
||||
QString key = name + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
if (pmCache.contains(key)) {
|
||||
return pmCache.value(key);
|
||||
}
|
||||
|
||||
QPixmap pixmap = tryLoadImage("theme:phases/" + name, QSize(height, height));
|
||||
|
||||
|
|
@ -95,19 +96,22 @@ QPixmap CounterPixmapGenerator::generatePixmap(int height, QString name, bool hi
|
|||
name = "general";
|
||||
}
|
||||
|
||||
if (highlight)
|
||||
if (highlight) {
|
||||
name.append("_highlight");
|
||||
}
|
||||
QString key = name + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
if (pmCache.contains(key)) {
|
||||
return pmCache.value(key);
|
||||
}
|
||||
|
||||
QPixmap pixmap = tryLoadImage("theme:counters/" + name, QSize(height, height));
|
||||
|
||||
// fall back to colorless counter if the name can't be found
|
||||
if (pixmap.isNull()) {
|
||||
name = "general";
|
||||
if (highlight)
|
||||
if (highlight) {
|
||||
name.append("_highlight");
|
||||
}
|
||||
pixmap = tryLoadImage("theme:counters/" + name, QSize(height, height));
|
||||
}
|
||||
|
||||
|
|
@ -118,17 +122,19 @@ QPixmap CounterPixmapGenerator::generatePixmap(int height, QString name, bool hi
|
|||
QPixmap PingPixmapGenerator::generatePixmap(int size, int value, int max)
|
||||
{
|
||||
int key = size * 1000000 + max * 1000 + value;
|
||||
if (pmCache.contains(key))
|
||||
if (pmCache.contains(key)) {
|
||||
return pmCache.value(key);
|
||||
}
|
||||
|
||||
QPixmap pixmap(size, size);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
QColor color;
|
||||
if ((max == -1) || (value == -1))
|
||||
if ((max == -1) || (value == -1)) {
|
||||
color = Qt::black;
|
||||
else
|
||||
} else {
|
||||
color.setHsv(120 * (1.0 - ((double)value / max)), 255, 255);
|
||||
}
|
||||
|
||||
QRadialGradient g(QPointF((double)pixmap.width() / 2, (double)pixmap.height() / 2),
|
||||
qMin(pixmap.width(), pixmap.height()) / 2.0);
|
||||
|
|
@ -145,11 +151,13 @@ QMap<int, QPixmap> PingPixmapGenerator::pmCache;
|
|||
|
||||
QPixmap CountryPixmapGenerator::generatePixmap(int height, const QString &countryCode)
|
||||
{
|
||||
if (countryCode.size() != 2)
|
||||
if (countryCode.size() != 2) {
|
||||
return QPixmap();
|
||||
}
|
||||
QString key = countryCode + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
if (pmCache.contains(key)) {
|
||||
return pmCache.value(key);
|
||||
}
|
||||
|
||||
int width = height * 2;
|
||||
QPixmap pixmap = tryLoadImage("theme:countries/" + countryCode.toLower(), QSize(width, height), true);
|
||||
|
|
@ -352,8 +360,9 @@ QPixmap LockPixmapGenerator::generatePixmap(int height)
|
|||
{
|
||||
|
||||
int key = height;
|
||||
if (pmCache.contains(key))
|
||||
if (pmCache.contains(key)) {
|
||||
return pmCache.value(key);
|
||||
}
|
||||
|
||||
QPixmap pixmap = tryLoadImage("theme:icons/lock", QSize(height, height), true);
|
||||
pmCache.insert(key, pixmap);
|
||||
|
|
@ -365,8 +374,9 @@ QMap<int, QPixmap> LockPixmapGenerator::pmCache;
|
|||
QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
|
||||
{
|
||||
QString key = QString::number(expanded) + ":" + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
if (pmCache.contains(key)) {
|
||||
return pmCache.value(key);
|
||||
}
|
||||
|
||||
QString name = expanded ? "dropdown_expanded" : "dropdown_collapsed";
|
||||
QPixmap pixmap = tryLoadImage("theme:icons/" + name, QSize(height, height), true);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file pixel_map_generator.h
|
||||
* @ingroup UI
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef PIXMAPGENERATOR_H
|
||||
#define PIXMAPGENERATOR_H
|
||||
|
|
|
|||
267
cockatrice/src/interface/theme_config.cpp
Normal file
267
cockatrice/src/interface/theme_config.cpp
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
#include "theme_config.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QMetaEnum>
|
||||
#include <QTextStream>
|
||||
|
||||
bool ThemeConfig::isEmpty() const
|
||||
{
|
||||
return colorScheme.isEmpty() && styleName.isEmpty();
|
||||
}
|
||||
|
||||
QString ThemeConfig::toIni() const
|
||||
{
|
||||
QString out;
|
||||
out += "[Appearance]\n";
|
||||
out += QString("ColorScheme = %1\n").arg(colorScheme.isEmpty() ? "System" : colorScheme);
|
||||
out += "\n[Style]\n";
|
||||
out += QString("Name = %1\n").arg(styleName.isEmpty() ? "Default" : styleName);
|
||||
return out;
|
||||
}
|
||||
|
||||
ThemeConfig ThemeConfig::fromThemeDir(const QString &themeDirPath)
|
||||
{
|
||||
ThemeConfig cfg;
|
||||
|
||||
if (themeDirPath.isEmpty()) {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
QFile f(QDir(themeDirPath).absoluteFilePath("theme.cfg"));
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
QString currentSection;
|
||||
|
||||
QTextStream in(&f);
|
||||
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine().trimmed();
|
||||
|
||||
if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith('[') && line.endsWith(']')) {
|
||||
currentSection = line.mid(1, line.length() - 2).trimmed();
|
||||
continue;
|
||||
}
|
||||
|
||||
int eq = line.indexOf('=');
|
||||
if (eq < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString key = line.left(eq).trimmed();
|
||||
QString value = line.mid(eq + 1).trimmed();
|
||||
|
||||
if (currentSection.compare("Appearance", Qt::CaseInsensitive) == 0) {
|
||||
if (key.compare("ColorScheme", Qt::CaseInsensitive) == 0) {
|
||||
cfg.colorScheme = value;
|
||||
}
|
||||
} else if (currentSection.compare("Style", Qt::CaseInsensitive) == 0) {
|
||||
if (key.compare("Name", Qt::CaseInsensitive) == 0) {
|
||||
cfg.styleName = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
bool ThemeConfig::save(const QString &themeDirPath) const
|
||||
{
|
||||
if (themeDirPath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir dir(themeDirPath);
|
||||
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
|
||||
QFile f(dir.absoluteFilePath("theme.cfg"));
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream out(&f);
|
||||
out << toIni();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PaletteConfig::hasPalette() const
|
||||
{
|
||||
return !colors.isEmpty();
|
||||
}
|
||||
|
||||
QString PaletteConfig::toToml() const
|
||||
{
|
||||
QMetaEnum roleEnum = QMetaEnum::fromType<QPalette::ColorRole>();
|
||||
|
||||
QString out;
|
||||
|
||||
static const QList<QPair<QPalette::ColorGroup, QString>> groups = {
|
||||
{QPalette::Active, "Palette"},
|
||||
{QPalette::Disabled, "Palette.Disabled"},
|
||||
{QPalette::Inactive, "Palette.Inactive"},
|
||||
};
|
||||
|
||||
for (const auto &[group, sectionName] : groups) {
|
||||
if (!colors.contains(group)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
out += QString("[%1]\n").arg(sectionName);
|
||||
|
||||
const auto &roleMap = colors[group];
|
||||
|
||||
for (auto it = roleMap.cbegin(); it != roleMap.cend(); ++it) {
|
||||
const char *roleName = roleEnum.valueToKey(it.key());
|
||||
|
||||
if (!roleName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
out += QString("%1 = %2\n").arg(QString(roleName), -20).arg(it.value().name(QColor::HexArgb));
|
||||
}
|
||||
|
||||
out += "\n";
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
QString PaletteConfig::fileName(const QString &colorScheme)
|
||||
{
|
||||
return colorScheme.compare("Dark", Qt::CaseInsensitive) == 0 ? "palette-dark.toml" : "palette-light.toml";
|
||||
}
|
||||
|
||||
PaletteConfig PaletteConfig::fromFile(const QString &filePath)
|
||||
{
|
||||
PaletteConfig cfg;
|
||||
|
||||
QFile f(filePath);
|
||||
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
QMetaEnum roleEnum = QMetaEnum::fromType<QPalette::ColorRole>();
|
||||
|
||||
QString currentSection;
|
||||
QPalette::ColorGroup currentGroup = QPalette::Active;
|
||||
|
||||
QTextStream in(&f);
|
||||
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine().trimmed();
|
||||
|
||||
if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith('[') && line.endsWith(']')) {
|
||||
currentSection = line.mid(1, line.length() - 2).trimmed();
|
||||
|
||||
if (currentSection.startsWith("Palette", Qt::CaseInsensitive)) {
|
||||
int dot = currentSection.indexOf('.');
|
||||
|
||||
QString groupStr = (dot >= 0) ? currentSection.mid(dot + 1) : "Active";
|
||||
|
||||
if (groupStr.compare("Disabled", Qt::CaseInsensitive) == 0) {
|
||||
currentGroup = QPalette::Disabled;
|
||||
} else if (groupStr.compare("Inactive", Qt::CaseInsensitive) == 0) {
|
||||
currentGroup = QPalette::Inactive;
|
||||
} else {
|
||||
currentGroup = QPalette::Active;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
int eq = line.indexOf('=');
|
||||
|
||||
if (eq < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString key = line.left(eq).trimmed();
|
||||
QString value = line.mid(eq + 1).trimmed();
|
||||
|
||||
// Strip inline comments if preceded by whitespace
|
||||
for (int i = 1; i < value.size(); ++i) {
|
||||
if (value[i] == '#' && value[i - 1].isSpace()) {
|
||||
value = value.left(i).trimmed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentSection.startsWith("Palette", Qt::CaseInsensitive)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key.startsWith("QPalette::")) {
|
||||
key = key.mid(10);
|
||||
}
|
||||
|
||||
int roleInt = roleEnum.keyToValue(key.toUtf8().constData());
|
||||
|
||||
if (roleInt < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QColor color(value);
|
||||
|
||||
if (color.isValid()) {
|
||||
cfg.colors[currentGroup][static_cast<QPalette::ColorRole>(roleInt)] = color;
|
||||
}
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
PaletteConfig PaletteConfig::fromScheme(const QString &themeDirPath, const QString &colorScheme)
|
||||
{
|
||||
if (themeDirPath.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return fromFile(QDir(themeDirPath).absoluteFilePath(fileName(colorScheme)));
|
||||
}
|
||||
|
||||
PaletteConfig PaletteConfig::fromDefault(const QString &themeDirPath, const QString &colorScheme)
|
||||
{
|
||||
if (themeDirPath.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QDir dir(themeDirPath);
|
||||
|
||||
bool wantDark = colorScheme.compare("Dark", Qt::CaseInsensitive) == 0;
|
||||
|
||||
PaletteConfig cfg =
|
||||
fromFile(dir.absoluteFilePath(wantDark ? "palette-default-dark.toml" : "palette-default-light.toml"));
|
||||
|
||||
if (!cfg.hasPalette()) {
|
||||
cfg = fromFile(dir.absoluteFilePath(wantDark ? "palette-default-light.toml" : "palette-default-dark.toml"));
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
QPalette PaletteConfig::apply(QPalette base) const
|
||||
{
|
||||
for (auto git = colors.cbegin(); git != colors.cend(); ++git) {
|
||||
for (auto rit = git.value().cbegin(); rit != git.value().cend(); ++rit) {
|
||||
base.setColor(git.key(), rit.key(), rit.value());
|
||||
}
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
37
cockatrice/src/interface/theme_config.h
Normal file
37
cockatrice/src/interface/theme_config.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef COCKATRICE_THEME_CONFIG_H
|
||||
#define COCKATRICE_THEME_CONFIG_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QMap>
|
||||
#include <QPalette>
|
||||
#include <QString>
|
||||
|
||||
struct ThemeConfig
|
||||
{
|
||||
QString colorScheme;
|
||||
QString styleName;
|
||||
|
||||
bool isEmpty() const;
|
||||
QString toIni() const;
|
||||
|
||||
static ThemeConfig fromThemeDir(const QString &themeDirPath);
|
||||
bool save(const QString &themeDirPath) const;
|
||||
};
|
||||
|
||||
struct PaletteConfig
|
||||
{
|
||||
QMap<QPalette::ColorGroup, QMap<QPalette::ColorRole, QColor>> colors;
|
||||
|
||||
bool hasPalette() const;
|
||||
QString toToml() const;
|
||||
|
||||
static QString fileName(const QString &colorScheme);
|
||||
|
||||
static PaletteConfig fromFile(const QString &filePath);
|
||||
static PaletteConfig fromScheme(const QString &themeDirPath, const QString &colorScheme);
|
||||
static PaletteConfig fromDefault(const QString &themeDirPath, const QString &colorScheme);
|
||||
|
||||
QPalette apply(QPalette base) const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_THEME_CONFIG_H
|
||||
|
|
@ -19,9 +19,7 @@
|
|||
#include <Qt>
|
||||
|
||||
#define NONE_THEME_NAME "Default"
|
||||
#define FUSION_THEME_NAME "Fusion (System Default)"
|
||||
#define FUSION_THEME_NAME_LIGHT "Fusion (Light)"
|
||||
#define FUSION_THEME_NAME_DARK "Fusion (Dark)"
|
||||
#define FUSION_THEME_NAME "Fusion"
|
||||
#define STYLE_CSS_NAME "style.css"
|
||||
#define HANDZONE_BG_NAME "handzone"
|
||||
#define PLAYERZONE_BG_NAME "playerzone"
|
||||
|
|
@ -51,8 +49,9 @@ struct PaletteColorInfo
|
|||
// Iterate through all color roles (excluding NoRole and NColorRoles)
|
||||
for (int r = 0; r < QPalette::NColorRoles; ++r) {
|
||||
auto role = static_cast<QPalette::ColorRole>(r);
|
||||
if (role == QPalette::NoRole)
|
||||
if (role == QPalette::NoRole) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PaletteColorInfo info;
|
||||
info.group = group;
|
||||
|
|
@ -78,8 +77,9 @@ struct PaletteColorInfo
|
|||
|
||||
for (int r = 0; r < QPalette::NColorRoles; ++r) {
|
||||
auto role = static_cast<QPalette::ColorRole>(r);
|
||||
if (role == QPalette::NoRole)
|
||||
if (role == QPalette::NoRole) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QColor color = palette.color(group, role);
|
||||
qInfo().nospace() << qPrintable(QString("%1").arg(roleEnum.valueToKey(role), -20)) << " : "
|
||||
|
|
@ -92,7 +92,7 @@ struct PaletteColorInfo
|
|||
ThemeManager::ThemeManager(QObject *parent) : QObject(parent)
|
||||
{
|
||||
defaultStyleName = qApp->style()->objectName();
|
||||
// FIXME workaround for windows11 style being broken
|
||||
//! \todo Workaround for windows11 style being broken.
|
||||
if (defaultStyleName == "windows11") {
|
||||
defaultStyleName = "windowsvista";
|
||||
}
|
||||
|
|
@ -113,37 +113,28 @@ void ThemeManager::ensureThemeDirectoryExists()
|
|||
}
|
||||
}
|
||||
|
||||
bool ThemeManager::isDarkMode()
|
||||
bool ThemeManager::isDarkMode(const QString &themeDirPath)
|
||||
{
|
||||
auto themeName = SettingsCache::instance().getThemeName();
|
||||
// Explicit Dark Mode
|
||||
if (themeName == FUSION_THEME_NAME_LIGHT || themeName.endsWith("(Light)")) {
|
||||
ThemeConfig themeConfig = ThemeConfig::fromThemeDir(themeDirPath);
|
||||
if (themeConfig.colorScheme.compare("Dark", Qt::CaseInsensitive) == 0) {
|
||||
return true;
|
||||
} else if (themeConfig.colorScheme.compare("Light", Qt::CaseInsensitive) == 0) {
|
||||
return false;
|
||||
}
|
||||
// Explicit Light Mode
|
||||
if (themeName == FUSION_THEME_NAME_DARK || themeName.endsWith("(Dark)")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Auto detection on compatible Qt versions
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
|
||||
if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark &&
|
||||
(themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME || themeName.endsWith("(System Default)"))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
bool osDark = (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark);
|
||||
#else
|
||||
bool osDark = false;
|
||||
#endif
|
||||
// Default to light mode
|
||||
return false;
|
||||
return osDark;
|
||||
}
|
||||
}
|
||||
|
||||
bool ThemeManager::isBuiltInTheme()
|
||||
{
|
||||
const auto themeName = SettingsCache::instance().getThemeName();
|
||||
|
||||
return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME || themeName == FUSION_THEME_NAME_LIGHT ||
|
||||
themeName == FUSION_THEME_NAME_DARK;
|
||||
return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME;
|
||||
}
|
||||
|
||||
QStringMap &ThemeManager::getAvailableThemes()
|
||||
|
|
@ -151,15 +142,13 @@ QStringMap &ThemeManager::getAvailableThemes()
|
|||
QDir dir;
|
||||
availableThemes.clear();
|
||||
|
||||
// add default value
|
||||
availableThemes.insert(NONE_THEME_NAME, QString());
|
||||
|
||||
// load themes from user profile dir
|
||||
dir.setPath(SettingsCache::instance().getThemesPath());
|
||||
|
||||
availableThemes.insert(FUSION_THEME_NAME, dir.filePath("Fusion (System Default)"));
|
||||
availableThemes.insert(FUSION_THEME_NAME_LIGHT, dir.filePath("Fusion (Light)"));
|
||||
availableThemes.insert(FUSION_THEME_NAME_DARK, dir.filePath("Fusion (Dark)"));
|
||||
// add default value
|
||||
availableThemes.insert(NONE_THEME_NAME, dir.absoluteFilePath("Default"));
|
||||
|
||||
availableThemes.insert(FUSION_THEME_NAME, dir.absoluteFilePath("Fusion"));
|
||||
|
||||
for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
if (!availableThemes.contains(themeName)) {
|
||||
|
|
@ -215,192 +204,112 @@ QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush)
|
|||
return brush;
|
||||
}
|
||||
|
||||
static inline QPalette createDarkGreenFusionPalette()
|
||||
ThemeConfig ThemeManager::loadGlobalConfig(const QString &themeDirPath)
|
||||
{
|
||||
QPalette p = QStyleFactory::create("Fusion")->standardPalette();
|
||||
|
||||
// ---------- Core backgrounds ----------
|
||||
p.setColor(QPalette::Window, QColor(30, 30, 30)); // #ff1e1e1e
|
||||
p.setColor(QPalette::Base, QColor(45, 45, 45)); // #ff2d2d2d
|
||||
p.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); // #ff353535
|
||||
p.setColor(QPalette::Button, QColor(60, 60, 60)); // #ff3c3c3c
|
||||
p.setColor(QPalette::ToolTipBase, QColor(60, 60, 60)); // #ff3c3c3c
|
||||
|
||||
// ---------- Core text ----------
|
||||
p.setColor(QPalette::WindowText, Qt::white); // #ffffffff
|
||||
p.setColor(QPalette::Text, Qt::white); // #ffffffff
|
||||
p.setColor(QPalette::ButtonText, Qt::white); // #ffffffff
|
||||
p.setColor(QPalette::ToolTipText, QColor(212, 212, 212)); // #ffd4d4d4
|
||||
p.setColor(QPalette::PlaceholderText, QColor(255, 255, 255, 128)); // #80ffffff
|
||||
|
||||
// ---------- Selection / focus ----------
|
||||
const QColor highlight(20, 140, 60); // #ff148c3c
|
||||
p.setColor(QPalette::Highlight, highlight);
|
||||
p.setColor(QPalette::HighlightedText, Qt::white); // #ffffffff
|
||||
|
||||
// ---------- Links ----------
|
||||
p.setColor(QPalette::Link, QColor(0, 246, 82)); // #ff00f652
|
||||
p.setColor(QPalette::LinkVisited, QColor(0, 211, 70)); // #ff00d346
|
||||
|
||||
// ---------- Accent (Qt 6) ----------
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
p.setColor(QPalette::Accent, QColor(0, 211, 70)); // #ff00d346
|
||||
#endif
|
||||
|
||||
// ---------- Bright text ----------
|
||||
p.setColor(QPalette::BrightText, QColor(0, 246, 82)); // #ff00f652
|
||||
|
||||
// ---------- 3D / frame shading ----------
|
||||
p.setColor(QPalette::Light, QColor(120, 120, 120)); // #ff787878
|
||||
p.setColor(QPalette::Midlight, QColor(90, 90, 90)); // #ff5a5a5a
|
||||
p.setColor(QPalette::Mid, QColor(40, 40, 40)); // #ff282828
|
||||
p.setColor(QPalette::Dark, QColor(30, 30, 30)); // #ff1e1e1e
|
||||
p.setColor(QPalette::Shadow, Qt::black); // #ff000000
|
||||
|
||||
// ---------- Disabled state ----------
|
||||
const QColor disabledText(157, 157, 157); // #ff9d9d9d
|
||||
p.setColor(QPalette::Disabled, QPalette::WindowText, disabledText);
|
||||
p.setColor(QPalette::Disabled, QPalette::Text, disabledText);
|
||||
p.setColor(QPalette::Disabled, QPalette::ButtonText, disabledText);
|
||||
p.setColor(QPalette::Disabled, QPalette::Base, QColor(30, 30, 30));
|
||||
p.setColor(QPalette::Disabled, QPalette::Window, QColor(30, 30, 30));
|
||||
p.setColor(QPalette::Disabled, QPalette::Link, QColor(48, 140, 198)); // #ff308cc6
|
||||
p.setColor(QPalette::Disabled, QPalette::LinkVisited, QColor(255, 0, 255)); // #ffff00ff
|
||||
p.setColor(QPalette::Disabled, QPalette::ToolTipBase, QColor(255, 255, 220));
|
||||
p.setColor(QPalette::Disabled, QPalette::ToolTipText, Qt::black);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
p.setColor(QPalette::Disabled, QPalette::Accent, disabledText);
|
||||
#endif
|
||||
|
||||
// ---------- Inactive state ----------
|
||||
p.setColor(QPalette::Inactive, QPalette::Highlight, QColor(30, 30, 30));
|
||||
p.setColor(QPalette::Inactive, QPalette::HighlightedText, Qt::white);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
p.setColor(QPalette::Inactive, QPalette::Accent, QColor(30, 30, 30));
|
||||
#endif
|
||||
|
||||
return p;
|
||||
return ThemeConfig::fromThemeDir(themeDirPath);
|
||||
}
|
||||
|
||||
static inline QPalette createLightGreenFusionPalette()
|
||||
bool ThemeManager::saveGlobalConfig(const QString &themeDirPath, const ThemeConfig &cfg)
|
||||
{
|
||||
QPalette p = QStyleFactory::create("Fusion")->standardPalette();
|
||||
|
||||
// ---------- Core backgrounds ----------
|
||||
p.setColor(QPalette::Window, QColor(240, 240, 240)); // #fff0f0f0
|
||||
p.setColor(QPalette::Base, Qt::white); // #ffffffff
|
||||
p.setColor(QPalette::AlternateBase, QColor(233, 231, 227)); // #ffe9e7e3
|
||||
p.setColor(QPalette::Button, QColor(240, 240, 240)); // #fff0f0f0
|
||||
p.setColor(QPalette::ToolTipBase, QColor(255, 255, 220)); // #ffffffdc
|
||||
|
||||
// ---------- Core text ----------
|
||||
p.setColor(QPalette::WindowText, Qt::black); // #ff000000
|
||||
p.setColor(QPalette::Text, Qt::black); // #ff000000
|
||||
p.setColor(QPalette::ButtonText, Qt::black); // #ff000000
|
||||
p.setColor(QPalette::ToolTipText, Qt::black); // #ff000000
|
||||
p.setColor(QPalette::PlaceholderText, QColor(0, 0, 0, 128)); // #80000000
|
||||
|
||||
// ---------- Selection / focus ----------
|
||||
const QColor highlight(20, 140, 60); // #ff148c3c
|
||||
p.setColor(QPalette::Highlight, highlight);
|
||||
p.setColor(QPalette::HighlightedText, Qt::white); // #ffffffff
|
||||
|
||||
// ---------- Links ----------
|
||||
p.setColor(QPalette::Link, QColor(13, 95, 40)); // #ff0d5f28
|
||||
p.setColor(QPalette::LinkVisited, QColor(8, 64, 27)); // #ff08401b
|
||||
|
||||
// ---------- Accent (Qt 6) ----------
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
p.setColor(QPalette::Accent, QColor(16, 117, 50)); // #ff107532
|
||||
#endif
|
||||
|
||||
// ---------- Bright text ----------
|
||||
p.setColor(QPalette::BrightText, Qt::white); // #ffffffff
|
||||
|
||||
// ---------- 3D / frame shading ----------
|
||||
p.setColor(QPalette::Light, Qt::white); // #ffffffff
|
||||
p.setColor(QPalette::Midlight, QColor(227, 227, 227)); // #ffe3e3e3
|
||||
p.setColor(QPalette::Mid, QColor(160, 160, 160)); // #ffa0a0a0
|
||||
p.setColor(QPalette::Dark, QColor(160, 160, 160)); // #ffa0a0a0
|
||||
p.setColor(QPalette::Shadow, QColor(105, 105, 105)); // #ff696969
|
||||
|
||||
// ---------- Disabled state ----------
|
||||
const QColor disabledText(120, 120, 120); // #ff787878
|
||||
p.setColor(QPalette::Disabled, QPalette::WindowText, disabledText);
|
||||
p.setColor(QPalette::Disabled, QPalette::Text, disabledText);
|
||||
p.setColor(QPalette::Disabled, QPalette::ButtonText, disabledText);
|
||||
p.setColor(QPalette::Disabled, QPalette::Base, QColor(240, 240, 240));
|
||||
p.setColor(QPalette::Disabled, QPalette::Window, QColor(240, 240, 240));
|
||||
p.setColor(QPalette::Disabled, QPalette::Midlight, QColor(247, 247, 247));
|
||||
p.setColor(QPalette::Disabled, QPalette::AlternateBase, QColor(247, 247, 247));
|
||||
p.setColor(QPalette::Disabled, QPalette::Shadow, Qt::black);
|
||||
p.setColor(QPalette::Disabled, QPalette::Link, QColor(0, 0, 255));
|
||||
p.setColor(QPalette::Disabled, QPalette::LinkVisited, QColor(255, 0, 255));
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
p.setColor(QPalette::Disabled, QPalette::Accent, disabledText);
|
||||
#endif
|
||||
|
||||
// ---------- Inactive state ----------
|
||||
p.setColor(QPalette::Inactive, QPalette::Highlight, QColor(240, 240, 240));
|
||||
p.setColor(QPalette::Inactive, QPalette::HighlightedText, Qt::black);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
p.setColor(QPalette::Inactive, QPalette::Accent, QColor(240, 240, 240));
|
||||
#endif
|
||||
|
||||
return p;
|
||||
return cfg.save(themeDirPath);
|
||||
}
|
||||
|
||||
void ThemeManager::themeChangedSlot()
|
||||
PaletteConfig ThemeManager::loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme)
|
||||
{
|
||||
QString themeName = SettingsCache::instance().getThemeName();
|
||||
qCInfo(ThemeManagerLog) << "Theme changed:" << themeName;
|
||||
if (themeDirPath.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
return PaletteConfig::fromScheme(themeDirPath, colorScheme);
|
||||
}
|
||||
|
||||
QString dirPath = getAvailableThemes().value(themeName);
|
||||
QDir dir = dirPath;
|
||||
|
||||
// css
|
||||
if (!dirPath.isEmpty() && dir.exists(STYLE_CSS_NAME)) {
|
||||
qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME));
|
||||
} else {
|
||||
qApp->setStyleSheet("");
|
||||
bool ThemeManager::savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg)
|
||||
{
|
||||
if (themeDirPath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QStyle *newStyle = nullptr;
|
||||
QPalette newPalette;
|
||||
QDir dir(themeDirPath);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
|
||||
if (themeName == FUSION_THEME_NAME) {
|
||||
newStyle = QStyleFactory::create("Fusion");
|
||||
QFile f(dir.absoluteFilePath(PaletteConfig::fileName(colorScheme)));
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
|
||||
// Start from Fusion's own palette so dark mode is handled correctly,
|
||||
// then apply any tweaks on top of it.
|
||||
newPalette = newStyle->standardPalette();
|
||||
if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark) {
|
||||
newPalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
|
||||
QTextStream(&f) << cfg.toToml();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ThemeManager::setColorScheme(const QString &scheme)
|
||||
{
|
||||
const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName());
|
||||
ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath);
|
||||
|
||||
cfg.colorScheme = scheme;
|
||||
|
||||
cfg.save(dirPath);
|
||||
reloadCurrentTheme();
|
||||
}
|
||||
|
||||
void ThemeManager::reloadCurrentTheme()
|
||||
{
|
||||
themeChangedSlot();
|
||||
}
|
||||
|
||||
void ThemeManager::previewPalette(const PaletteConfig &cfg, const QString &scheme)
|
||||
{
|
||||
const QString themeName = SettingsCache::instance().getThemeName();
|
||||
const QString dirPath = getAvailableThemes().value(themeName);
|
||||
const ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath);
|
||||
applyStyleAndPalette(themeName, themeCfg, cfg, scheme);
|
||||
}
|
||||
|
||||
void ThemeManager::applyStyleAndPalette(const QString &themeName,
|
||||
const ThemeConfig &themeCfg,
|
||||
const PaletteConfig &palCfg,
|
||||
const QString &activeScheme)
|
||||
{
|
||||
QString styleName = themeCfg.styleName;
|
||||
if (styleName.isEmpty() || styleName.compare("Default", Qt::CaseInsensitive) == 0) {
|
||||
if (themeName == FUSION_THEME_NAME) {
|
||||
styleName = "Fusion";
|
||||
} else {
|
||||
styleName = defaultStyleName;
|
||||
}
|
||||
#else
|
||||
newPalette = qApp->palette();
|
||||
#endif
|
||||
} else if (themeName == FUSION_THEME_NAME_LIGHT) {
|
||||
newStyle = QStyleFactory::create("Fusion");
|
||||
newPalette = createLightGreenFusionPalette();
|
||||
} else if (themeName == FUSION_THEME_NAME_DARK) {
|
||||
newStyle = QStyleFactory::create("Fusion");
|
||||
newPalette = createDarkGreenFusionPalette();
|
||||
} else {
|
||||
newStyle = QStyleFactory::create(defaultStyleName);
|
||||
// Use the style's default palette.
|
||||
newPalette = newStyle->standardPalette();
|
||||
}
|
||||
|
||||
// Apply palette FIRST.
|
||||
qApp->setPalette(newPalette);
|
||||
// Then apply style.
|
||||
qApp->setStyle(newStyle);
|
||||
QStyle *style = QStyleFactory::create(styleName);
|
||||
if (!style) {
|
||||
style = QStyleFactory::create(defaultStyleName);
|
||||
}
|
||||
|
||||
// Base palette
|
||||
QPalette base;
|
||||
if (styleName.compare("Fusion", Qt::CaseInsensitive) == 0) {
|
||||
base = style->standardPalette();
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
|
||||
if (activeScheme == "Dark") {
|
||||
base.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
base = qApp->palette();
|
||||
}
|
||||
|
||||
// Overlay custom palette colours
|
||||
if (palCfg.hasPalette()) {
|
||||
base = palCfg.apply(base);
|
||||
}
|
||||
|
||||
// Palette BEFORE style — setStyle() triggers a synchronous repolish of all
|
||||
// widgets immediately. If the palette isn't set yet at that point, every
|
||||
// widget gets polished against the stale colours, requiring a second apply
|
||||
// to fully resolve. Setting palette first means setStyle's repolish cascade
|
||||
// already sees the correct colours.
|
||||
qApp->setPalette(base);
|
||||
qApp->setStyle(style);
|
||||
|
||||
// Force every widget to re-polish and repaint immediately rather than
|
||||
// waiting for natural expose events, which produces a patchwork of old
|
||||
|
|
@ -410,34 +319,57 @@ void ThemeManager::themeChangedSlot()
|
|||
// palette (WA_SetPalette not set). Calling it unconditionally would clobber
|
||||
// intentional per-widget palette customisations across the whole app.
|
||||
for (QWidget *widget : qApp->allWidgets()) {
|
||||
if (widget->isVisible()) {
|
||||
newStyle->unpolish(widget);
|
||||
newStyle->polish(widget);
|
||||
|
||||
widget->update();
|
||||
}
|
||||
style->unpolish(widget);
|
||||
style->polish(widget);
|
||||
widget->update();
|
||||
}
|
||||
}
|
||||
|
||||
if (dirPath.isEmpty()) {
|
||||
// set default values
|
||||
QDir::setSearchPaths("theme", DEFAULT_RESOURCE_PATHS);
|
||||
brushes[Role::Hand] = HANDZONE_BG_DEFAULT;
|
||||
brushes[Role::Table] = TABLEZONE_BG_DEFAULT;
|
||||
brushes[Role::Player] = PLAYERZONE_BG_DEFAULT;
|
||||
brushes[Role::Stack] = STACKZONE_BG_DEFAULT;
|
||||
void ThemeManager::themeChangedSlot()
|
||||
{
|
||||
QString themeName = SettingsCache::instance().getThemeName();
|
||||
QString dirPath = getAvailableThemes().value(themeName);
|
||||
currentThemePath = dirPath;
|
||||
QDir dir(dirPath);
|
||||
|
||||
// CSS
|
||||
if (!dirPath.isEmpty() && dir.exists(STYLE_CSS_NAME)) {
|
||||
qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME));
|
||||
} else {
|
||||
// resources
|
||||
QStringList resources;
|
||||
resources << dir.absolutePath() << DEFAULT_RESOURCE_PATHS;
|
||||
QDir::setSearchPaths("theme", resources);
|
||||
|
||||
// zones bg
|
||||
dir.cd("zones");
|
||||
brushes[Role::Hand] = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT);
|
||||
brushes[Role::Table] = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT);
|
||||
brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
|
||||
brushes[Role::Stack] = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT);
|
||||
qApp->setStyleSheet("");
|
||||
}
|
||||
|
||||
// load theme.cfg for style + scheme preference
|
||||
ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath);
|
||||
|
||||
// Resolve active scheme:
|
||||
// theme.cfg says Dark/Light → use that
|
||||
// theme.cfg says System or is absent → follow the OS
|
||||
QString activeScheme = isDarkMode(dirPath) ? "Dark" : "Light";
|
||||
|
||||
// ── Load palette: custom first, then theme default ────────────────────
|
||||
PaletteConfig palette = PaletteConfig::fromScheme(dirPath, activeScheme);
|
||||
if (!palette.hasPalette()) {
|
||||
palette = PaletteConfig::fromDefault(dirPath, activeScheme);
|
||||
}
|
||||
|
||||
applyStyleAndPalette(themeName, themeCfg, palette, activeScheme);
|
||||
|
||||
QStringList resources;
|
||||
if (!dirPath.isEmpty()) {
|
||||
resources << dir.absolutePath();
|
||||
}
|
||||
resources << DEFAULT_RESOURCE_PATHS;
|
||||
|
||||
QDir::setSearchPaths("theme", resources);
|
||||
|
||||
brushes[Role::Hand] = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT);
|
||||
|
||||
brushes[Role::Table] = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT);
|
||||
|
||||
brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
|
||||
|
||||
brushes[Role::Stack] = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT);
|
||||
for (auto &brushCache : brushesCache) {
|
||||
brushCache.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
/**
|
||||
* @file theme_manager.h
|
||||
* @ingroup CoreSettings
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef THEMEMANAGER_H
|
||||
#define THEMEMANAGER_H
|
||||
|
||||
#include "theme_config.h"
|
||||
|
||||
#include <QBrush>
|
||||
#include <QDir>
|
||||
#include <QLoggingCategory>
|
||||
|
|
@ -41,6 +43,7 @@ public:
|
|||
|
||||
private:
|
||||
QString defaultStyleName;
|
||||
QString currentThemePath;
|
||||
std::array<QBrush, Role::MaxRole + 1> brushes;
|
||||
QStringMap availableThemes;
|
||||
/*
|
||||
|
|
@ -52,11 +55,31 @@ protected:
|
|||
void ensureThemeDirectoryExists();
|
||||
QBrush loadBrush(QString fileName, QColor fallbackColor);
|
||||
QBrush loadExtraBrush(QString fileName, QBrush &fallbackBrush);
|
||||
void applyStyleAndPalette(const QString &themeName,
|
||||
const ThemeConfig &themeCfg,
|
||||
const PaletteConfig &palCfg,
|
||||
const QString &activeScheme);
|
||||
|
||||
public:
|
||||
bool isBuiltInTheme();
|
||||
bool isDarkMode();
|
||||
bool isDarkMode(const QString &themeDirPath);
|
||||
QStringMap &getAvailableThemes();
|
||||
// Returns the path to the currently active theme directory (empty = default)
|
||||
QString getCurrentThemePath() const
|
||||
{
|
||||
return currentThemePath;
|
||||
}
|
||||
// Load the global theme settings (style + color scheme preference)
|
||||
static ThemeConfig loadGlobalConfig(const QString &themeDirPath);
|
||||
static bool saveGlobalConfig(const QString &themeDirPath, const ThemeConfig &cfg);
|
||||
|
||||
// Load/save per-scheme palette colors
|
||||
static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme);
|
||||
static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg);
|
||||
void setColorScheme(const QString &scheme);
|
||||
|
||||
void reloadCurrentTheme();
|
||||
void previewPalette(const PaletteConfig &cfg, const QString &scheme);
|
||||
|
||||
QBrush &getBgBrush(Role zone);
|
||||
QBrush getExtraBgBrush(Role zone, int zoneId = 0);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file color_identity_widget.h
|
||||
* @ingroup CardExtraInfoWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef COLOR_IDENTITY_WIDGET_H
|
||||
#define COLOR_IDENTITY_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file mana_cost_widget.h
|
||||
* @ingroup CardExtraInfoWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef MANA_COST_WIDGET_H
|
||||
#define MANA_COST_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file mana_symbol_widget.h
|
||||
* @ingroup CardExtraInfoWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef MANA_SYMBOL_WIDGET_H
|
||||
#define MANA_SYMBOL_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -95,8 +95,9 @@ void CardGroupDisplayWidget::onSelectionChanged(const QItemSelection &selected,
|
|||
for (auto &range : deselected) {
|
||||
for (int row = range.top(); row <= range.bottom(); ++row) {
|
||||
QModelIndex idx = range.model()->index(row, 0, range.parent());
|
||||
if (proxyModel)
|
||||
if (proxyModel) {
|
||||
idx = proxyModel->mapToSource(idx);
|
||||
}
|
||||
|
||||
auto it = indexToWidgetMap.find(QPersistentModelIndex(idx));
|
||||
if (it != indexToWidgetMap.end()) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file card_group_display_widget.h
|
||||
* @ingroup DeckEditorCardGroupWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef CARD_GROUP_DISPLAY_WIDGET_H
|
||||
#define CARD_GROUP_DISPLAY_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file flat_card_group_display_widget.h
|
||||
* @ingroup DeckEditorCardGroupWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef FLAT_CARD_GROUP_DISPLAY_WIDGET_H
|
||||
#define FLAT_CARD_GROUP_DISPLAY_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file overlapped_card_group_display_widget.h
|
||||
* @ingroup DeckEditorCardGroupWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H
|
||||
#define OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -43,11 +43,13 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *pa
|
|||
|
||||
void CardInfoDisplayWidget::setCard(const ExactCard &card)
|
||||
{
|
||||
if (exactCard)
|
||||
if (exactCard) {
|
||||
disconnect(exactCard.getCardPtr().data(), nullptr, this, nullptr);
|
||||
}
|
||||
exactCard = card;
|
||||
if (exactCard)
|
||||
if (exactCard) {
|
||||
connect(exactCard.getCardPtr().data(), &QObject::destroyed, this, &CardInfoDisplayWidget::clear);
|
||||
}
|
||||
|
||||
text->setCard(exactCard);
|
||||
pic->setCard(exactCard);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file card_info_display_widget.h
|
||||
* @ingroup CardWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef CARDINFOWIDGET_H
|
||||
#define CARDINFOWIDGET_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file card_info_frame_widget.h
|
||||
* @ingroup CardWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef CARDFRAME_H
|
||||
#define CARDFRAME_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file card_info_picture_art_crop_widget.h
|
||||
* @ingroup CardWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef CARD_INFO_PICTURE_ART_CROP_WIDGET_H
|
||||
#define CARD_INFO_PICTURE_ART_CROP_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* @file card_info_picture_enlarged_widget.h
|
||||
* @ingroup CardWigets
|
||||
* @ingroup DeckEditorWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef CARD_PICTURE_ENLARGED_WIDGET_H
|
||||
#define CARD_PICTURE_ENLARGED_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file card_info_picture_widget.h
|
||||
* @ingroup CardWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef CARD_INFO_PICTURE_H
|
||||
#define CARD_INFO_PICTURE_H
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* @file card_info_picture_with_text_overlay_widget.h
|
||||
* @ingroup CardWidgets
|
||||
* @ingroup DeckStorageWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef CARD_PICTURE_WITH_TEXT_OVERLAY_H
|
||||
#define CARD_PICTURE_WITH_TEXT_OVERLAY_H
|
||||
|
|
|
|||
|
|
@ -73,8 +73,9 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard)
|
|||
|
||||
QStringList cardProps = card->getProperties();
|
||||
for (const QString &key : cardProps) {
|
||||
if (key.contains("-"))
|
||||
if (key.contains("-")) {
|
||||
continue;
|
||||
}
|
||||
QString keyText = Mtg::getNicePropertyName(key).toHtmlEscaped() + ":";
|
||||
text +=
|
||||
QString("<tr><td>%1</td><td></td><td>%2</td></tr>").arg(keyText, card->getProperty(key).toHtmlEscaped());
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file card_info_text_widget.h
|
||||
* @ingroup CardWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef CARDINFOTEXT_H
|
||||
#define CARDINFOTEXT_H
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
* @ingroup CardWidgets
|
||||
* @ingroup DeckEditorWidgets
|
||||
* @ingroup DeckStorageWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef CARD_SIZE_WIDGET_H
|
||||
#define CARD_SIZE_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file deck_card_zone_display_widget.h
|
||||
* @ingroup DeckEditorWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DECK_CARD_ZONE_DISPLAY_WIDGET_H
|
||||
#define DECK_CARD_ZONE_DISPLAY_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* @file deck_preview_card_picture_widget.h
|
||||
* @ingroup CardWidgets
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DECK_PREVIEW_CARD_PICTURE_WIDGET_H
|
||||
#define DECK_PREVIEW_CARD_PICTURE_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ AbstractAnalyticsPanelWidget *
|
|||
AnalyticsPanelWidgetFactory::create(const QString &type, QWidget *parent, DeckListStatisticsAnalyzer *analyzer) const
|
||||
{
|
||||
auto it = widgets.find(type);
|
||||
if (it == widgets.end())
|
||||
if (it == widgets.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto w = it->creator(parent, analyzer);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,11 @@
|
|||
DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer)
|
||||
: AbstractAnalyticsPanelWidget(parent, analyzer)
|
||||
{
|
||||
controls = new QWidget(this);
|
||||
controlLayout = new QHBoxLayout(controls);
|
||||
controlLayout->setContentsMargins(11, 0, 11, 0);
|
||||
controls = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
|
||||
controls->setSpacing(4, 4);
|
||||
|
||||
labelPrefix = new QLabel(this);
|
||||
controlLayout->addWidget(labelPrefix);
|
||||
controls->addWidget(labelPrefix);
|
||||
|
||||
criteriaCombo = new QComboBox(this);
|
||||
// Give these things item-data so we can translate the actual user-facing strings
|
||||
|
|
@ -33,33 +32,32 @@ DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatistics
|
|||
criteriaCombo->addItem(QString(), "type");
|
||||
criteriaCombo->addItem(QString(), "subtype");
|
||||
criteriaCombo->addItem(QString(), "cmc");
|
||||
controlLayout->addWidget(criteriaCombo);
|
||||
controls->addWidget(criteriaCombo);
|
||||
|
||||
exactnessCombo = new QComboBox(this);
|
||||
exactnessCombo->addItem(QString(), true); // At least
|
||||
exactnessCombo->addItem(QString(), false); // Exactly
|
||||
controlLayout->addWidget(exactnessCombo);
|
||||
controls->addWidget(exactnessCombo);
|
||||
|
||||
quantitySpin = new QSpinBox(this);
|
||||
quantitySpin->setRange(1, 60);
|
||||
controlLayout->addWidget(quantitySpin);
|
||||
controls->addWidget(quantitySpin);
|
||||
|
||||
labelMiddle = new QLabel(this);
|
||||
controlLayout->addWidget(labelMiddle);
|
||||
controls->addWidget(labelMiddle);
|
||||
|
||||
drawnSpin = new QSpinBox(this);
|
||||
drawnSpin->setRange(1, 60);
|
||||
drawnSpin->setValue(7);
|
||||
controlLayout->addWidget(drawnSpin);
|
||||
controls->addWidget(drawnSpin);
|
||||
|
||||
labelSuffix = new QLabel(this);
|
||||
controlLayout->addWidget(labelSuffix);
|
||||
controls->addWidget(labelSuffix);
|
||||
|
||||
labelPrefix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
labelMiddle->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
labelSuffix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
|
||||
controlLayout->addStretch(1);
|
||||
layout->addWidget(controls);
|
||||
|
||||
// Table
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef COCKATRICE_DRAW_PROBABILITY_WIDGET_H
|
||||
#define COCKATRICE_DRAW_PROBABILITY_WIDGET_H
|
||||
|
||||
#include "../../../../layouts/flow_layout.h"
|
||||
#include "../../../general/layout_containers/flow_widget.h"
|
||||
#include "../../abstract_analytics_panel_widget.h"
|
||||
#include "../../deck_list_statistics_analyzer.h"
|
||||
#include "draw_probability_config.h"
|
||||
|
|
@ -31,8 +33,7 @@ private slots:
|
|||
private:
|
||||
DrawProbabilityConfig config;
|
||||
|
||||
QWidget *controls;
|
||||
QHBoxLayout *controlLayout;
|
||||
FlowWidget *controls;
|
||||
QLabel *labelPrefix;
|
||||
QLabel *labelMiddle;
|
||||
QLabel *labelSuffix;
|
||||
|
|
|
|||
|
|
@ -28,8 +28,9 @@ ManaBaseConfigDialog::ManaBaseConfigDialog(DeckListStatisticsAnalyzer *analyzer,
|
|||
|
||||
// select initial filters
|
||||
for (int i = 0; i < filterList->count(); ++i) {
|
||||
if (config.filters.contains(filterList->item(i)->text()))
|
||||
if (config.filters.contains(filterList->item(i)->text())) {
|
||||
filterList->item(i)->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file mana_base_widget.h
|
||||
* @ingroup DeckEditorAnalyticsWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef MANA_BASE_WIDGET_H
|
||||
#define MANA_BASE_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -69,8 +69,9 @@ void ManaCurveConfigDialog::setFromConfig(const ManaCurveConfig &cfg)
|
|||
{
|
||||
groupBy->setCurrentText(cfg.groupBy);
|
||||
// restore filters
|
||||
for (int i = 0; i < filterList->count(); ++i)
|
||||
for (int i = 0; i < filterList->count(); ++i) {
|
||||
filterList->item(i)->setSelected(cfg.filters.contains(filterList->item(i)->text()));
|
||||
}
|
||||
|
||||
showMain->setChecked(cfg.showMain);
|
||||
showCatRows->setChecked(cfg.showCategoryRows);
|
||||
|
|
@ -81,8 +82,9 @@ void ManaCurveConfigDialog::accept()
|
|||
cfg.groupBy = groupBy->currentText();
|
||||
|
||||
cfg.filters.clear();
|
||||
for (auto *item : filterList->selectedItems())
|
||||
for (auto *item : filterList->selectedItems()) {
|
||||
cfg.filters << item->text();
|
||||
}
|
||||
|
||||
cfg.showMain = showMain->isChecked();
|
||||
cfg.showCategoryRows = showCatRows->isChecked();
|
||||
|
|
|
|||
|
|
@ -51,15 +51,17 @@ void ManaCurveTotalWidget::updateDisplay(const QString &categoryName,
|
|||
for (auto it = cmcIt->cbegin(); it != cmcIt->cend(); ++it) {
|
||||
const QString &category = it.key();
|
||||
|
||||
if (!config.filters.isEmpty() && !config.filters.contains(category))
|
||||
if (!config.filters.isEmpty() && !config.filters.contains(category)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int value = it.value();
|
||||
|
||||
QStringList cards;
|
||||
const auto catIt = cardsMap.constFind(category);
|
||||
if (catIt != cardsMap.cend())
|
||||
if (catIt != cardsMap.cend()) {
|
||||
cards = catIt->value(cmc);
|
||||
}
|
||||
|
||||
segments.push_back({category, value, cards, GameSpecificColors::MTG::colorHelper(category)});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,16 +69,18 @@ static void buildMapsByCategory(const QHash<QString, QHash<int, int>> &categoryC
|
|||
const QString &category = catIt.key();
|
||||
const auto &countsByCmc = catIt.value();
|
||||
|
||||
for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it)
|
||||
for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it) {
|
||||
outCmcMap[it.key()][category] = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto catIt = categoryCards.cbegin(); catIt != categoryCards.cend(); ++catIt) {
|
||||
const QString &category = catIt.key();
|
||||
const auto &cardsByCmc = catIt.value();
|
||||
|
||||
for (auto it = cardsByCmc.cbegin(); it != cardsByCmc.cend(); ++it)
|
||||
for (auto it = cardsByCmc.cbegin(); it != cardsByCmc.cend(); ++it) {
|
||||
outCardsMap[category][it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,8 +90,9 @@ static void findGlobalCmcRange(const QHash<QString, QHash<int, int>> &categoryCo
|
|||
maxCmc = 0;
|
||||
|
||||
for (const auto &countsByCmc : categoryCounts) {
|
||||
for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it)
|
||||
for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it) {
|
||||
maxCmc = qMax(maxCmc, it.key());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file mana_curve_widget.h
|
||||
* @ingroup DeckEditorAnalyticsWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef MANA_CURVE_WIDGET_H
|
||||
#define MANA_CURVE_WIDGET_H
|
||||
|
|
@ -37,7 +37,7 @@ public:
|
|||
{
|
||||
config = ManaCurveConfig::fromJson(o);
|
||||
updateDisplay();
|
||||
};
|
||||
}
|
||||
|
||||
QJsonObject extractConfigFromDialog(QDialog *dlg) const override;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,9 @@ ManaDevotionConfigDialog::ManaDevotionConfigDialog(DeckListStatisticsAnalyzer *a
|
|||
|
||||
// select initial filters
|
||||
for (int i = 0; i < filterList->count(); ++i) {
|
||||
if (config.filters.contains(filterList->item(i)->text()))
|
||||
if (config.filters.contains(filterList->item(i)->text())) {
|
||||
filterList->item(i)->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file mana_devotion_widget.h
|
||||
* @ingroup DeckEditorAnalyticsWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef MANA_DEVOTION_WIDGET_H
|
||||
#define MANA_DEVOTION_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ ManaDistributionConfig ManaDistributionConfig::fromJson(const QJsonObject &o)
|
|||
|
||||
if (o.contains("filters")) {
|
||||
config.filters.clear();
|
||||
for (auto v : o["filters"].toArray())
|
||||
for (auto v : o["filters"].toArray()) {
|
||||
config.filters << v.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (o.contains("showColorRows")) {
|
||||
|
|
|
|||
|
|
@ -62,8 +62,9 @@ void ManaDistributionConfigDialog::setFromConfig(const ManaDistributionConfig &c
|
|||
|
||||
displayType->setCurrentText(cfg.displayType);
|
||||
|
||||
for (int i = 0; i < filterList->count(); ++i)
|
||||
for (int i = 0; i < filterList->count(); ++i) {
|
||||
filterList->item(i)->setSelected(cfg.filters.contains(filterList->item(i)->text()));
|
||||
}
|
||||
|
||||
showColorRows->setChecked(cfg.showColorRows);
|
||||
}
|
||||
|
|
@ -74,8 +75,9 @@ void ManaDistributionConfigDialog::accept()
|
|||
|
||||
// Filters
|
||||
cfg.filters.clear();
|
||||
for (auto *item : filterList->selectedItems())
|
||||
for (auto *item : filterList->selectedItems()) {
|
||||
cfg.filters << item->text();
|
||||
}
|
||||
|
||||
cfg.showColorRows = showColorRows->isChecked();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
|
|||
layout = new QVBoxLayout(this);
|
||||
|
||||
// Controls
|
||||
controlContainer = new QWidget(this);
|
||||
controlLayout = new QHBoxLayout(controlContainer);
|
||||
controlContainer = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
|
||||
controlContainer->setSpacing(4, 4);
|
||||
addButton = new QPushButton(this);
|
||||
removeButton = new QPushButton(this);
|
||||
saveButton = new QPushButton(this);
|
||||
|
|
@ -32,11 +32,11 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
|
|||
includeSideboardCheckBox = new QCheckBox(this);
|
||||
includeSideboardCheckBox->setChecked(false);
|
||||
|
||||
controlLayout->addWidget(addButton);
|
||||
controlLayout->addWidget(removeButton);
|
||||
controlLayout->addWidget(saveButton);
|
||||
controlLayout->addWidget(loadButton);
|
||||
controlLayout->addWidget(includeSideboardCheckBox);
|
||||
controlContainer->addWidget(addButton);
|
||||
controlContainer->addWidget(removeButton);
|
||||
controlContainer->addWidget(saveButton);
|
||||
controlContainer->addWidget(loadButton);
|
||||
controlContainer->addWidget(includeSideboardCheckBox);
|
||||
|
||||
layout->addWidget(controlContainer);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef DECK_ANALYTICS_WIDGET_H
|
||||
#define DECK_ANALYTICS_WIDGET_H
|
||||
|
||||
#include "../general/layout_containers/flow_widget.h"
|
||||
#include "abstract_analytics_panel_widget.h"
|
||||
#include "deck_list_statistics_analyzer.h"
|
||||
#include "resizable_panel.h"
|
||||
|
|
@ -51,8 +52,7 @@ private:
|
|||
void addPanelInstance(const QString &typeId, AbstractAnalyticsPanelWidget *panel, const QJsonObject &cfg = {});
|
||||
|
||||
QVBoxLayout *layout;
|
||||
QWidget *controlContainer;
|
||||
QHBoxLayout *controlLayout;
|
||||
FlowWidget *controlContainer;
|
||||
|
||||
QPushButton *addButton;
|
||||
QPushButton *removeButton;
|
||||
|
|
|
|||
|
|
@ -191,10 +191,12 @@ double DeckListStatisticsAnalyzer::hypergeometric(int N, int K, int n, int k)
|
|||
}
|
||||
|
||||
auto choose = [](int n, int r) -> double {
|
||||
if (r > n)
|
||||
if (r > n) {
|
||||
return 0.0;
|
||||
if (r == 0 || r == n)
|
||||
}
|
||||
if (r == 0 || r == n) {
|
||||
return 1.0;
|
||||
}
|
||||
double res = 1.0;
|
||||
for (int i = 1; i <= r; ++i) {
|
||||
res *= (n - r + i);
|
||||
|
|
|
|||
|
|
@ -58,7 +58,3 @@ void DeckEditorCardDatabaseDockWidget::clearAllDatabaseFilters()
|
|||
{
|
||||
databaseDisplayWidget->clearAllDatabaseFilters();
|
||||
}
|
||||
void DeckEditorCardDatabaseDockWidget::highlightAllSearchEdit()
|
||||
{
|
||||
databaseDisplayWidget->searchEdit->setSelection(0, databaseDisplayWidget->searchEdit->text().length());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ public:
|
|||
public slots:
|
||||
void retranslateUi();
|
||||
void clearAllDatabaseFilters();
|
||||
void highlightAllSearchEdit();
|
||||
|
||||
private:
|
||||
void createDatabaseDisplayDock(AbstractTabDeckEditor *deckEditor);
|
||||
|
|
|
|||
|
|
@ -121,9 +121,10 @@ void DeckEditorDatabaseDisplayWidget::updateSearch(const QString &search)
|
|||
{
|
||||
databaseDisplayModel->setStringFilter(search);
|
||||
QModelIndexList sel = databaseView->selectionModel()->selectedRows();
|
||||
if (sel.isEmpty() && databaseDisplayModel->rowCount())
|
||||
if (sel.isEmpty() && databaseDisplayModel->rowCount()) {
|
||||
databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0),
|
||||
QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters()
|
||||
|
|
@ -147,11 +148,13 @@ void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex ¤t, con
|
|||
|
||||
void DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck()
|
||||
{
|
||||
highlightAllSearchEdit();
|
||||
emit addCardToMainDeck(currentCard());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::actAddCardToSideboard()
|
||||
{
|
||||
highlightAllSearchEdit();
|
||||
emit addCardToSideboard(currentCard());
|
||||
}
|
||||
|
||||
|
|
@ -240,4 +243,9 @@ void DeckEditorDatabaseDisplayWidget::retranslateUi()
|
|||
{
|
||||
aAddCard->setText(tr("Add card to &maindeck"));
|
||||
aAddCardToSideboard->setText(tr("Add card to &sideboard"));
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::highlightAllSearchEdit()
|
||||
{
|
||||
searchEdit->setSelection(0, searchEdit->text().length());
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ class DeckEditorDatabaseDisplayWidget : public QWidget
|
|||
public:
|
||||
explicit DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor);
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
SearchLineEdit *searchEdit;
|
||||
CardDatabaseModel *databaseModel;
|
||||
CardDatabaseDisplayModel *databaseDisplayModel;
|
||||
|
||||
|
|
@ -58,10 +57,13 @@ private:
|
|||
KeySignals searchKeySignals;
|
||||
QTreeView *databaseView;
|
||||
QHBoxLayout *searchLayout;
|
||||
SearchLineEdit *searchEdit;
|
||||
QAction *aAddCard, *aAddCardToSideboard;
|
||||
QVBoxLayout *centralFrame;
|
||||
QWidget *centralWidget;
|
||||
|
||||
void highlightAllSearchEdit();
|
||||
|
||||
private slots:
|
||||
void retranslateUi();
|
||||
void saveDbHeaderState();
|
||||
|
|
|
|||
|
|
@ -314,8 +314,9 @@ void DeckEditorDeckDockWidget::initializeFormats()
|
|||
ExactCard DeckEditorDeckDockWidget::getCurrentCard()
|
||||
{
|
||||
QModelIndex current = deckView->selectionModel()->currentIndex();
|
||||
if (!current.isValid())
|
||||
if (!current.isValid()) {
|
||||
return {};
|
||||
}
|
||||
const QString cardName = current.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
||||
const QString cardProviderID = current.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
|
||||
const QModelIndex gparent = current.parent().parent();
|
||||
|
|
@ -530,7 +531,7 @@ void DeckEditorDeckDockWidget::changeSelectedCard(int changeBy)
|
|||
// currentIndex will return an index for the underlying deckModel instead of the proxy.
|
||||
// That index will return an invalid index when indexBelow/indexAbove crosses a header node,
|
||||
// causing the selection to fail to move down.
|
||||
/// \todo Figure out why it's happening so we can do a proper fix instead of a hacky workaround
|
||||
//! \todo Figure out why it's happening so we can do a proper fix instead of a hacky workaround.
|
||||
if (deckViewCurrentIndex.model() == proxy->sourceModel()) {
|
||||
deckViewCurrentIndex = proxy->mapFromSource(deckViewCurrentIndex);
|
||||
}
|
||||
|
|
@ -634,8 +635,8 @@ void DeckEditorDeckDockWidget::actSwapSelection()
|
|||
{
|
||||
auto selectedRows = getSelectedCardNodeSourceIndices();
|
||||
|
||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||
//! \todo Remove the hack and also handle reselection when all rows of a multi-selection gets deleted.
|
||||
// Hack: maintains old reselection behavior when single-selection row is deleted.
|
||||
if (selectedRows.length() == 1) {
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
|
@ -651,10 +652,12 @@ void DeckEditorDeckDockWidget::actSwapSelection()
|
|||
|
||||
void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString zoneName)
|
||||
{
|
||||
if (!card)
|
||||
if (!card) {
|
||||
return;
|
||||
if (card.getInfo().getIsToken())
|
||||
}
|
||||
if (card.getInfo().getIsToken()) {
|
||||
zoneName = DECK_ZONE_TOKENS;
|
||||
}
|
||||
|
||||
deckStateManager->decrementCard(card, zoneName);
|
||||
}
|
||||
|
|
@ -663,8 +666,8 @@ void DeckEditorDeckDockWidget::actDecrementSelection()
|
|||
{
|
||||
auto selectedRows = getSelectedCardNodeSourceIndices();
|
||||
|
||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||
//! \todo Remove the hack and also handle reselection when all rows of a multi-selection gets deleted.
|
||||
// Hack: maintains old reselection behavior when single-selection row is deleted.
|
||||
if (selectedRows.length() == 1) {
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
|
@ -680,8 +683,8 @@ void DeckEditorDeckDockWidget::actRemoveCard()
|
|||
{
|
||||
auto selectedRows = getSelectedCardNodeSourceIndices();
|
||||
|
||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||
//! \todo Remove the hack and also handle reselection when all rows of a multi-selection gets deleted.
|
||||
// Hack: maintains old reselection behavior when single-selection row is deleted.
|
||||
if (selectedRows.length() == 1) {
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,8 +89,9 @@ void DeckEditorFilterDockWidget::filterViewCustomContextMenu(const QPoint &point
|
|||
QModelIndex idx;
|
||||
|
||||
idx = filterView->indexAt(point);
|
||||
if (!idx.isValid())
|
||||
if (!idx.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
action = menu.addAction(QString("delete"));
|
||||
action->setData(point);
|
||||
|
|
@ -105,8 +106,9 @@ void DeckEditorFilterDockWidget::filterRemove(const QAction *action)
|
|||
|
||||
point = action->data().toPoint();
|
||||
idx = filterView->indexAt(point);
|
||||
if (!idx.isValid())
|
||||
if (!idx.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
filterModel->removeRow(idx.row(), idx.parent());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@
|
|||
QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
QModelIndex src = mapToSource(index);
|
||||
if (!src.isValid())
|
||||
if (!src.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant value = QIdentityProxyModel::data(index, role);
|
||||
|
||||
|
|
|
|||
|
|
@ -192,8 +192,9 @@ QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zone
|
|||
|
||||
QModelIndex DeckStateManager::decrementCard(const ExactCard &card, const QString &zoneName)
|
||||
{
|
||||
if (!card)
|
||||
if (!card) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QString providerId = card.getPrinting().getUuid();
|
||||
QString collectorNumber = card.getPrinting().getProperty("num");
|
||||
|
|
@ -241,15 +242,17 @@ static bool doSwapCard(DeckListModel *model,
|
|||
|
||||
bool DeckStateManager::swapCardAtIndex(const QModelIndex &idx)
|
||||
{
|
||||
if (!idx.isValid())
|
||||
if (!idx.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
||||
QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
|
||||
QModelIndex gparent = idx.parent().parent();
|
||||
|
||||
if (!gparent.isValid())
|
||||
if (!gparent.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||
QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
|
||||
|
|
|
|||
|
|
@ -154,9 +154,11 @@ public:
|
|||
*/
|
||||
QModelIndex modifyDeck(const QString &reason, const std::function<QModelIndex(DeckListModel *)> &operation);
|
||||
|
||||
/// @name Metadata setters
|
||||
/// @brief These methods set the metadata. Will no-op if the new value is the same as the current value.
|
||||
/// Saves the operation to history if successful.
|
||||
/**
|
||||
* @name Metadata setters
|
||||
* @brief These methods set the metadata. Will no-op if the new value is the same as the current value.
|
||||
* Saves the operation to history if successful.
|
||||
*/
|
||||
///@{
|
||||
void setName(const QString &name);
|
||||
void setComments(const QString &comments);
|
||||
|
|
|
|||
|
|
@ -261,13 +261,7 @@ void DlgConnect::updateDisplayInfo(const QString &saveName)
|
|||
QStringList _data = uci.getServerInfo(saveName);
|
||||
|
||||
if (_data.isEmpty()) {
|
||||
_data << ""
|
||||
<< ""
|
||||
<< ""
|
||||
<< ""
|
||||
<< ""
|
||||
<< ""
|
||||
<< "";
|
||||
_data << "" << "" << "" << "" << "" << "" << "";
|
||||
}
|
||||
|
||||
bool savePasswordStatus = (_data.at(5) == "1");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file dlg_connect.h
|
||||
* @ingroup ConnectionDialogs
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_CONNECT_H
|
||||
#define DLG_CONNECT_H
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* @file dlg_convert_deck_to_cod_format.h
|
||||
* @ingroup LocalDeckStorageDialogs
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DIALOG_CONVERT_DECK_TO_COD_FORMAT_H
|
||||
#define DIALOG_CONVERT_DECK_TO_COD_FORMAT_H
|
||||
|
|
|
|||
|
|
@ -215,8 +215,9 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QS
|
|||
spectatorsSeeEverythingCheckBox->setChecked(gameInfo.spectators_omniscient());
|
||||
|
||||
QSet<int> types;
|
||||
for (int i = 0; i < gameInfo.game_types_size(); ++i)
|
||||
for (int i = 0; i < gameInfo.game_types_size(); ++i) {
|
||||
types.insert(gameInfo.game_types(i));
|
||||
}
|
||||
|
||||
QMapIterator<int, QString> gameTypeIterator(gameTypes);
|
||||
while (gameTypeIterator.hasNext()) {
|
||||
|
|
@ -316,9 +317,9 @@ void DlgCreateGame::checkResponse(const Response &response)
|
|||
{
|
||||
buttonBox->setEnabled(true);
|
||||
|
||||
if (response.response_code() == Response::RespOk)
|
||||
if (response.response_code() == Response::RespOk) {
|
||||
accept();
|
||||
else {
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Server error."));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file dlg_create_game.h
|
||||
* @ingroup RoomDialogs
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_CREATEGAME_H
|
||||
#define DLG_CREATEGAME_H
|
||||
|
|
|
|||
|
|
@ -95,8 +95,9 @@ void DlgDefaultTagsEditor::addItem()
|
|||
// Prevent duplicate tags
|
||||
for (int i = 0; i < listWidget->count(); ++i) {
|
||||
QWidget *widget = listWidget->itemWidget(listWidget->item(i));
|
||||
if (!widget)
|
||||
if (!widget) {
|
||||
continue;
|
||||
}
|
||||
QLineEdit *lineEdit = widget->findChild<QLineEdit *>();
|
||||
if (lineEdit && lineEdit->text() == newTag) {
|
||||
QMessageBox::warning(this, tr("Duplicate Tag"), tr("This tag already exists."));
|
||||
|
|
@ -138,8 +139,9 @@ void DlgDefaultTagsEditor::confirmChanges()
|
|||
QStringList updatedList;
|
||||
for (int i = 0; i < listWidget->count(); ++i) {
|
||||
QWidget *widget = listWidget->itemWidget(listWidget->item(i));
|
||||
if (!widget)
|
||||
if (!widget) {
|
||||
continue;
|
||||
}
|
||||
QLineEdit *lineEdit = widget->findChild<QLineEdit *>();
|
||||
if (lineEdit) {
|
||||
updatedList.append(lineEdit->text());
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* @file dlg_default_tags_editor.h
|
||||
* @ingroup Dialogs
|
||||
* @ingroup DeckStorageWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_DEFAULT_TAGS_EDITOR_H
|
||||
#define DLG_DEFAULT_TAGS_EDITOR_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file dlg_edit_avatar.h
|
||||
* @ingroup AccountDialogs
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_EDITAVATAR_H
|
||||
#define DLG_EDITAVATAR_H
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent)
|
|||
|
||||
void DlgEditPassword::actOk()
|
||||
{
|
||||
//! \todo this stuff should be using qvalidators
|
||||
//! \todo This stuff should be using QValidators.
|
||||
if (newPasswordEdit->text().length() < 8) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Your password is too short."));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file dlg_edit_password.h
|
||||
* @ingroup AccountDialogs
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_EDITPASSWORD_H
|
||||
#define DLG_EDITPASSWORD_H
|
||||
|
|
|
|||
|
|
@ -149,8 +149,9 @@ void DlgEditTokens::actAddToken()
|
|||
QString name;
|
||||
for (;;) {
|
||||
name = getTextWithMax(this, tr("Add token"), tr("Please enter the name of the token:"));
|
||||
if (name.isEmpty())
|
||||
if (name.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (databaseModel->getDatabase()->query()->getCardInfo(name)) {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("The chosen name conflicts with an existing card or token.\nMake sure to enable "
|
||||
|
|
@ -181,18 +182,21 @@ void DlgEditTokens::actRemoveToken()
|
|||
|
||||
void DlgEditTokens::colorChanged(int colorIndex)
|
||||
{
|
||||
if (currentCard)
|
||||
if (currentCard) {
|
||||
currentCard->setColors(QString(colorEdit->itemData(colorIndex).toChar()));
|
||||
}
|
||||
}
|
||||
|
||||
void DlgEditTokens::ptChanged(const QString &_pt)
|
||||
{
|
||||
if (currentCard)
|
||||
if (currentCard) {
|
||||
currentCard->setPowTough(_pt);
|
||||
}
|
||||
}
|
||||
|
||||
void DlgEditTokens::annotationChanged(const QString &_annotation)
|
||||
{
|
||||
if (currentCard)
|
||||
if (currentCard) {
|
||||
currentCard->setText(_annotation);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file dlg_edit_tokens.h
|
||||
* @ingroup GameDialogs
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_EDIT_TOKENS_H
|
||||
#define DLG_EDIT_TOKENS_H
|
||||
|
|
|
|||
|
|
@ -26,8 +26,9 @@ DlgEditUser::DlgEditUser(QWidget *parent, QString email, QString country, QStrin
|
|||
int i = 1;
|
||||
for (const QString &c : countries) {
|
||||
countryEdit->addItem(QPixmap("theme:countries/" + c.toLower()), c);
|
||||
if (c == country)
|
||||
if (c == country) {
|
||||
countryEdit->setCurrentIndex(i);
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file dlg_edit user.h
|
||||
* @ingroup NetworkDialogs
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_EDITUSER_H
|
||||
#define DLG_EDITUSER_H
|
||||
|
|
|
|||
|
|
@ -87,8 +87,9 @@ DlgFilterGames::DlgFilterGames(const QMap<int, QString> &_allGameTypes,
|
|||
if (!allGameTypes.isEmpty()) {
|
||||
gameTypeFilterGroupBox = new QGroupBox(tr("&Game types"));
|
||||
gameTypeFilterGroupBox->setLayout(gameTypeFilterLayout);
|
||||
} else
|
||||
} else {
|
||||
gameTypeFilterGroupBox = nullptr;
|
||||
}
|
||||
|
||||
auto *maxPlayersFilterMinLabel = new QLabel(tr("at &least:"));
|
||||
maxPlayersFilterMinSpinBox = new QSpinBox;
|
||||
|
|
@ -226,8 +227,9 @@ QSet<int> DlgFilterGames::getGameTypeFilter() const
|
|||
QMapIterator<int, QCheckBox *> i(gameTypeFilterCheckBoxes);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (i.value()->isChecked())
|
||||
if (i.value()->isChecked()) {
|
||||
result.insert(i.key());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file dlg_filter_games.h
|
||||
* @ingroup RoomDialogs
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_FILTER_GAMES_H
|
||||
#define DLG_FILTER_GAMES_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file dlg_forgot_password_challenge.h
|
||||
* @ingroup AccountDialogs
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_FORGOTPASSWORDCHALLENGE_H
|
||||
#define DLG_FORGOTPASSWORDCHALLENGE_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file dlg_forgot_password_request.h
|
||||
* @ingroup AccountDialogs
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_FORGOTPASSWORDREQUEST_H
|
||||
#define DLG_FORGOTPASSWORDREQUEST_H
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ void DlgForgotPasswordReset::actOk()
|
|||
return;
|
||||
}
|
||||
|
||||
//! \todo this stuff should be using qvalidators
|
||||
//! \todo This stuff should be using QValidators.
|
||||
if (newpasswordEdit->text().length() < 8) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Your password is too short."));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file dlg_forgot_password_reset.h
|
||||
* @ingroup AccountDialogs
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_FORGOTPASSWORDRESET_H
|
||||
#define DLG_FORGOTPASSWORDRESET_H
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* @file dlg_load_deck.h
|
||||
* @ingroup LocalDeckStorageDialogs
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_LOAD_DECK_H
|
||||
#define DLG_LOAD_DECK_H
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* @file dlg_load_deck_from_clipboard.h
|
||||
* @ingroup LocalDeckStorageDialogs
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_LOAD_DECK_FROM_CLIPBOARD_H
|
||||
#define DLG_LOAD_DECK_FROM_CLIPBOARD_H
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* @file dlg_load_deck_from_website.h
|
||||
* @ingroup RemoteDeckStorageDialogs
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef DLG_LOAD_DECK_FROM_WEBSITE_H
|
||||
#define DLG_LOAD_DECK_FROM_WEBSITE_H
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue