mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-12 09:04:53 -07:00
* move message_log_widget to game * move files * update headers * fix cmakelists * oracle fixes * split implementation out to cpp * fix recursive import * fix main file * format
This commit is contained in:
parent
f484c98152
commit
17dcaf9afa
337 changed files with 728 additions and 721 deletions
664
cockatrice/src/interface/layouts/flow_layout.cpp
Normal file
664
cockatrice/src/interface/layouts/flow_layout.cpp
Normal file
|
|
@ -0,0 +1,664 @@
|
|||
/**
|
||||
* @file flow_layout.cpp
|
||||
* @brief Implementation of the FlowLayout class, a custom layout for dynamically organizing widgets
|
||||
* in rows within the constraints of available width or parent scroll areas.
|
||||
*/
|
||||
|
||||
#include "flow_layout.h"
|
||||
|
||||
#include "../widgets/general/layout_containers/flow_widget.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QLayoutItem>
|
||||
#include <QScrollArea>
|
||||
#include <QStyle>
|
||||
|
||||
/**
|
||||
* @brief Constructs a FlowLayout instance with the specified parent widget, margin, and spacing values.
|
||||
* @param parent The parent widget for this layout.
|
||||
* @param margin The layout margin.
|
||||
* @param hSpacing The horizontal spacing between items.
|
||||
* @param vSpacing The vertical spacing between items.
|
||||
*/
|
||||
FlowLayout::FlowLayout(QWidget *parent,
|
||||
const Qt::Orientation _flowDirection,
|
||||
const int margin,
|
||||
const int hSpacing,
|
||||
const int 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;
|
||||
while ((item = FlowLayout::takeAt(0))) {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Indicates the layout's support for expansion in both horizontal and vertical directions.
|
||||
* @return The orientations (Qt::Horizontal | Qt::Vertical) this layout can expand to fill.
|
||||
*/
|
||||
Qt::Orientations FlowLayout::expandingDirections() const
|
||||
{
|
||||
return Qt::Horizontal | Qt::Vertical;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Indicates that this layout's height depends on its width.
|
||||
* @return True, as the layout adjusts its height to fit the specified width.
|
||||
*/
|
||||
bool FlowLayout::hasHeightForWidth() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the required height to display all items within the specified width.
|
||||
* @param width The available width for arranging items.
|
||||
* @return The total height needed to fit all items in rows constrained by the specified width.
|
||||
*/
|
||||
int FlowLayout::heightForWidth(const int width) const
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
|
||||
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()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint(); // The suggested size for the current item
|
||||
int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
void FlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y)
|
||||
{
|
||||
for (QLayoutItem *item : rowItems) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the maximum allowed size for the item
|
||||
QSize itemMaxSize = item->widget()->maximumSize();
|
||||
// Constrain the item's width and height to its size hint or maximum size
|
||||
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
|
||||
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
|
||||
// Set the item's geometry based on the computed size and position
|
||||
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
|
||||
// Move the x-position to the right, leaving space for horizontal spacing
|
||||
x += itemWidth + horizontalSpacing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lays out items into columns according to the available height, starting from a given origin.
|
||||
* Each column is arranged within `availableHeight`, wrapping to a new column as necessary.
|
||||
* @param originX The x-coordinate for the layout start position.
|
||||
* @param originY The y-coordinate for the layout start position.
|
||||
* @param availableHeight The height within which each column is constrained.
|
||||
* @return The total width after arranging all columns.
|
||||
*/
|
||||
int 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
|
||||
|
||||
for (QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint(); // The suggested size for the current item
|
||||
|
||||
// Check if the current item fits in the remaining height of the current column
|
||||
if (currentYPosition + itemSize.height() > availableHeight) {
|
||||
// If not, layout the current column and start a new column
|
||||
layoutSingleColumn(colItems, currentXPosition, originY);
|
||||
colItems.clear(); // Reset the list for the new column
|
||||
currentYPosition = originY; // Reset y-position to the column's start
|
||||
currentXPosition += colWidth; // Move x-position to the next column
|
||||
colWidth = 0; // Reset column width for the new column
|
||||
}
|
||||
|
||||
// Add the item to the current column
|
||||
colItems.append(item);
|
||||
colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item
|
||||
currentYPosition += itemSize.height(); // Move y-position for the next item
|
||||
}
|
||||
|
||||
// Layout the final column if there are any remaining items
|
||||
layoutSingleColumn(colItems, currentXPosition, originY);
|
||||
|
||||
// Return the total width used, including the last column's width
|
||||
return currentXPosition + colWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arranges a single column of items within specified x and y starting positions.
|
||||
* @param colItems A list of items to be arranged in the column.
|
||||
* @param x The starting x-coordinate for the column.
|
||||
* @param y The starting y-coordinate for the column.
|
||||
*/
|
||||
void FlowLayout::layoutSingleColumn(const QVector<QLayoutItem *> &colItems, const int x, int y)
|
||||
{
|
||||
for (QLayoutItem *item : colItems) {
|
||||
if (item == nullptr) {
|
||||
qCDebug(FlowLayoutLog) << "Item is null.";
|
||||
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.";
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
currentWidth += itemWidth;
|
||||
rowHeight = qMax(rowHeight, itemSize.height());
|
||||
qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight;
|
||||
}
|
||||
|
||||
// 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 minimum size for horizontal flow direction.
|
||||
* @return A QSize representing the minimum required dimensions.
|
||||
*/
|
||||
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
|
||||
|
||||
const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth();
|
||||
|
||||
qCDebug(FlowLayoutLog) << "Calculating horizontal minimum size. Available width:" << availableWidth;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
currentWidth += itemWidth;
|
||||
rowHeight = qMax(rowHeight, itemMinSize.height());
|
||||
qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight;
|
||||
}
|
||||
|
||||
// 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.
|
||||
*/
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 Adds a new item to the layout.
|
||||
* @param item The layout item to add.
|
||||
*/
|
||||
void FlowLayout::addItem(QLayoutItem *item)
|
||||
{
|
||||
if (item != nullptr) {
|
||||
items.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves the count of items in the layout.
|
||||
* @return The number of layout items.
|
||||
*/
|
||||
int FlowLayout::count() const
|
||||
{
|
||||
return static_cast<int>(items.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the layout item at the specified index.
|
||||
* @param index The index of the item to retrieve.
|
||||
* @return A pointer to the item at the specified index, or nullptr if out of range.
|
||||
*/
|
||||
QLayoutItem *FlowLayout::itemAt(const int index) const
|
||||
{
|
||||
return (index >= 0 && index < items.size()) ? items.value(index) : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes and returns the item at the specified index.
|
||||
* @param index The index of the item to remove.
|
||||
* @return A pointer to the removed item, or nullptr if out of range.
|
||||
*/
|
||||
QLayoutItem *FlowLayout::takeAt(const int index)
|
||||
{
|
||||
return (index >= 0 && index < items.size()) ? items.takeAt(index) : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the horizontal spacing between items.
|
||||
* @return The horizontal spacing if set, otherwise a smart default.
|
||||
*/
|
||||
int FlowLayout::horizontalSpacing() const
|
||||
{
|
||||
return (horizontalMargin >= 0) ? horizontalMargin : smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the vertical spacing between items.
|
||||
* @return The vertical spacing if set, otherwise a smart default.
|
||||
*/
|
||||
int FlowLayout::verticalSpacing() const
|
||||
{
|
||||
return (verticalMargin >= 0) ? verticalMargin : smartSpacing(QStyle::PM_LayoutVerticalSpacing);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates smart spacing based on the parent widget style.
|
||||
* @param pm The pixel metric to calculate.
|
||||
* @return The calculated spacing value.
|
||||
*/
|
||||
int FlowLayout::smartSpacing(const QStyle::PixelMetric pm) const
|
||||
{
|
||||
QObject *parent = this->parent();
|
||||
|
||||
if (!parent) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (parent->isWidgetType()) {
|
||||
const auto *pw = dynamic_cast<QWidget *>(parent);
|
||||
return pw->style()->pixelMetric(pm, nullptr, pw);
|
||||
}
|
||||
|
||||
return dynamic_cast<QLayout *>(parent)->spacing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the width of the parent scroll area, if any.
|
||||
* @return The width of the scroll area's viewport, or 0 if not found.
|
||||
*/
|
||||
int FlowLayout::getParentScrollAreaWidth() const
|
||||
{
|
||||
QWidget *parent = parentWidget();
|
||||
|
||||
while (parent) {
|
||||
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
|
||||
return scrollArea->viewport()->width();
|
||||
}
|
||||
parent = parent->parentWidget();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the height of the parent scroll area, if any.
|
||||
* @return The height of the scroll area's viewport, or 0 if not found.
|
||||
*/
|
||||
int FlowLayout::getParentScrollAreaHeight() const
|
||||
{
|
||||
QWidget *parent = parentWidget();
|
||||
|
||||
while (parent) {
|
||||
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
|
||||
return scrollArea->viewport()->height();
|
||||
}
|
||||
parent = parent->parentWidget();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
54
cockatrice/src/interface/layouts/flow_layout.h
Normal file
54
cockatrice/src/interface/layouts/flow_layout.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef FLOW_LAYOUT_H
|
||||
#define FLOW_LAYOUT_H
|
||||
|
||||
#include <QLayout>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QWidget>
|
||||
#include <qstyle.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(FlowLayoutLog, "flow_layout", QtInfoMsg);
|
||||
|
||||
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() override;
|
||||
void insertWidgetAtIndex(QWidget *toInsert, int index);
|
||||
|
||||
QSize calculateMinimumSizeHorizontal() const;
|
||||
QSize calculateSizeHintVertical() const;
|
||||
QSize calculateMinimumSizeVertical() const;
|
||||
void addItem(QLayoutItem *item) override;
|
||||
[[nodiscard]] int count() const override;
|
||||
[[nodiscard]] QLayoutItem *itemAt(int index) const override;
|
||||
QLayoutItem *takeAt(int index) override;
|
||||
[[nodiscard]] int horizontalSpacing() const;
|
||||
|
||||
[[nodiscard]] Qt::Orientations expandingDirections() const override;
|
||||
[[nodiscard]] bool hasHeightForWidth() const override;
|
||||
[[nodiscard]] int heightForWidth(int width) const override;
|
||||
[[nodiscard]] int verticalSpacing() const;
|
||||
[[nodiscard]] int doLayout(const QRect &rect, bool testOnly) const;
|
||||
[[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const;
|
||||
[[nodiscard]] int getParentScrollAreaWidth() const;
|
||||
[[nodiscard]] int getParentScrollAreaHeight() const;
|
||||
|
||||
void setGeometry(const QRect &rect) override;
|
||||
virtual int layoutAllRows(int originX, int originY, int availableWidth);
|
||||
virtual void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y);
|
||||
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;
|
||||
QSize calculateSizeHintHorizontal() const;
|
||||
|
||||
protected:
|
||||
QList<QLayoutItem *> items; // List to store layout items
|
||||
Qt::Orientation flowDirection;
|
||||
int horizontalMargin;
|
||||
int verticalMargin;
|
||||
};
|
||||
|
||||
#endif // FLOW_LAYOUT_H
|
||||
496
cockatrice/src/interface/layouts/overlap_layout.cpp
Normal file
496
cockatrice/src/interface/layouts/overlap_layout.cpp
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
#include "overlap_layout.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QtMath>
|
||||
|
||||
/**
|
||||
* @class OverlapLayout
|
||||
* @brief Custom layout class to arrange widgets with overlapping positions.
|
||||
*
|
||||
* The OverlapLayout class is a QLayout subclass that arranges child widgets
|
||||
* in an overlapping configuration, allowing control over the overlap percentage
|
||||
* and the number of rows or columns based on the chosen layout direction. This
|
||||
* layout is particularly useful for visualizing elements that need to partially
|
||||
* stack over one another, either horizontally or vertically.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Constructs an OverlapLayout with the specified parameters.
|
||||
*
|
||||
* Initializes a new OverlapLayout with the given overlap percentage, row or column limit,
|
||||
* and layout direction. The overlap percentage determines how much each widget will
|
||||
* overlap with the previous one. If maxColumns or maxRows are set to zero, it implies
|
||||
* no limit in that respective dimension.
|
||||
*
|
||||
* @param overlapPercentage An integer representing the percentage of overlap between items (0-100).
|
||||
* @param maxColumns The maximum number of columns allowed in the layout when in horizontal orientation (0 for
|
||||
* unlimited).
|
||||
* @param maxRows The maximum number of rows allowed in the layout when in vertical orientation (0 for unlimited).
|
||||
* @param direction The orientation direction of the layout, either Qt::Horizontal or Qt::Vertical.
|
||||
* @param parent The parent widget of this layout.
|
||||
*/
|
||||
OverlapLayout::OverlapLayout(QWidget *parent,
|
||||
const int overlapPercentage,
|
||||
const int maxColumns,
|
||||
const int maxRows,
|
||||
const Qt::Orientation _overlapDirection,
|
||||
const Qt::Orientation _flowDirection)
|
||||
: QLayout(parent), overlapPercentage(overlapPercentage), maxColumns(maxColumns), maxRows(maxRows),
|
||||
overlapDirection(_overlapDirection), flowDirection(_flowDirection)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor for OverlapLayout, ensuring cleanup of all layout items.
|
||||
*
|
||||
* Iterates through all layout items and deletes them. This prevents memory
|
||||
* leaks by removing all child QLayoutItems stored in the layout.
|
||||
*/
|
||||
OverlapLayout::~OverlapLayout()
|
||||
{
|
||||
QLayoutItem *item;
|
||||
while ((item = OverlapLayout::takeAt(0))) {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
void OverlapLayout::insertWidgetAtIndex(QWidget *toInsert, int index)
|
||||
{
|
||||
addChildWidget(toInsert);
|
||||
int clampedIndex = qBound(0, index, qMax(0, static_cast<int>(itemList.size())));
|
||||
itemList.insert(clampedIndex, new QWidgetItem(toInsert));
|
||||
|
||||
for (int i = clampedIndex; i < itemList.size(); ++i) {
|
||||
dynamic_cast<QWidgetItem *>(itemList.at(i))->widget()->raise();
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a new item to the layout.
|
||||
*
|
||||
* Appends a QLayoutItem to the internal list, allowing it to be positioned within the
|
||||
* layout during the next geometry update. This method does not directly arrange the
|
||||
* items; it merely adds them to the layout’s tracking.
|
||||
*
|
||||
* @param item Pointer to the QLayoutItem being added to this layout.
|
||||
*/
|
||||
void OverlapLayout::addItem(QLayoutItem *item)
|
||||
{
|
||||
if (item != nullptr) {
|
||||
itemList.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves the total count of items within the layout.
|
||||
*
|
||||
* Returns the number of items stored in the layout's internal item list.
|
||||
* This count reflects how many widgets or spacers the layout is currently managing.
|
||||
*
|
||||
* @return Integer count of items in the layout.
|
||||
*/
|
||||
int OverlapLayout::count() const
|
||||
{
|
||||
return static_cast<int>(itemList.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides access to a layout item at a specified index.
|
||||
*
|
||||
* Allows retrieval of a QLayoutItem from the layout’s internal list
|
||||
* by index. If the index is out of bounds, this function will return nullptr.
|
||||
*
|
||||
* @param index The index of the desired item.
|
||||
* @return Pointer to the QLayoutItem at the specified index, or nullptr if index is invalid.
|
||||
*/
|
||||
QLayoutItem *OverlapLayout::itemAt(const int index) const
|
||||
{
|
||||
return (index >= 0 && index < itemList.size()) ? itemList.value(index) : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes and returns a layout item at the specified index.
|
||||
*
|
||||
* Removes a QLayoutItem from the layout at the given index, reducing the layout's count.
|
||||
* If the index is invalid, this function returns nullptr without any effect.
|
||||
*
|
||||
* @param index The index of the item to remove.
|
||||
* @return Pointer to the removed QLayoutItem, or nullptr if index is invalid.
|
||||
*/
|
||||
QLayoutItem *OverlapLayout::takeAt(const int index)
|
||||
{
|
||||
return (index >= 0 && index < itemList.size()) ? itemList.takeAt(index) : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the geometry for the layout items, arranging them with the specified overlap.
|
||||
* @param rect The rectangle defining the area within which the layout should arrange items.
|
||||
*/
|
||||
void OverlapLayout::setGeometry(const QRect &rect)
|
||||
{
|
||||
// Call the base class implementation to ensure standard layout behavior.
|
||||
QLayout::setGeometry(rect);
|
||||
|
||||
// If there are no items to layout, exit early.
|
||||
if (itemList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parent widget for size and margin calculations.
|
||||
const QWidget *parentWidget = this->parentWidget();
|
||||
if (!parentWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate available width and height, subtracting the parent's margins.
|
||||
int availableWidth = parentWidget->width();
|
||||
int availableHeight = parentWidget->height();
|
||||
const QMargins margins = parentWidget->contentsMargins();
|
||||
availableWidth -= margins.left() + margins.right();
|
||||
availableHeight -= margins.top() + margins.bottom();
|
||||
|
||||
// Determine the maximum item width and height among all layout items.
|
||||
int maxItemWidth = 0;
|
||||
int maxItemHeight = 0;
|
||||
for (QLayoutItem *item : itemList) {
|
||||
if (item != nullptr && item->widget()) {
|
||||
QSize itemSize = item->widget()->sizeHint();
|
||||
maxItemWidth = qMax(maxItemWidth, itemSize.width());
|
||||
maxItemHeight = qMax(maxItemHeight, itemSize.height());
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the overlap offsets based on the layout direction and overlap percentage.
|
||||
const int overlapOffsetWidth = (overlapDirection == Qt::Horizontal) ? (maxItemWidth * overlapPercentage / 100) : 0;
|
||||
const int overlapOffsetHeight = (overlapDirection == Qt::Vertical) ? (maxItemHeight * overlapPercentage / 100) : 0;
|
||||
|
||||
// Determine the number of columns based on layout constraints and available space.
|
||||
int columns;
|
||||
if (flowDirection == Qt::Horizontal) {
|
||||
if (maxColumns > 0) {
|
||||
// Calculate the maximum possible columns given the available width and overlap.
|
||||
const int availableColumns = (availableWidth + overlapOffsetWidth) / (maxItemWidth - overlapOffsetWidth);
|
||||
// Use the smaller of maxColumns and availableColumns.
|
||||
qCDebug(OverlapLayoutLog) << " Max Columns " << maxColumns << " available columns " << availableColumns;
|
||||
columns = qMin(maxColumns, availableColumns);
|
||||
if (columns < 1) {
|
||||
columns = 1;
|
||||
}
|
||||
} else {
|
||||
// If no maxColumns constraint, allow as many columns as possible.
|
||||
columns = INT_MAX;
|
||||
}
|
||||
} else {
|
||||
// If not a horizontal layout, column count is irrelevant.
|
||||
columns = INT_MAX;
|
||||
}
|
||||
|
||||
// Determine the number of rows based on layout constraints and available space.
|
||||
int rows;
|
||||
if (flowDirection == Qt::Vertical) {
|
||||
if (maxRows > 0) {
|
||||
// Calculate the maximum possible rows given the available height and overlap.
|
||||
const int availableRows = (availableHeight + overlapOffsetHeight) / (maxItemHeight - overlapOffsetHeight);
|
||||
// Use the smaller of maxRows and availableRows.
|
||||
qCDebug(OverlapLayoutLog) << " Max Rows " << maxRows << " available rows " << availableRows;
|
||||
rows = qMin(maxRows, availableRows);
|
||||
if (rows < 1) {
|
||||
rows = 1;
|
||||
}
|
||||
} else {
|
||||
// If no maxRows constraint, allow as many rows as possible.
|
||||
rows = INT_MAX;
|
||||
}
|
||||
} else {
|
||||
// If not a vertical layout, row count is irrelevant.
|
||||
rows = INT_MAX;
|
||||
}
|
||||
|
||||
// Initialize row and column indices.
|
||||
int currentRow = 0;
|
||||
int currentColumn = 0;
|
||||
|
||||
// Loop through all items and position them based on the calculated offsets.
|
||||
for (const auto item : itemList) {
|
||||
if (item == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate the position of the current item.
|
||||
const int xPos = rect.left() + currentColumn * (maxItemWidth - overlapOffsetWidth);
|
||||
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?
|
||||
// Update row and column indices based on the layout direction.
|
||||
if (overlapDirection == Qt::Horizontal) {
|
||||
currentColumn++;
|
||||
if (currentColumn > qCeil(itemList.size() / rows)) {
|
||||
currentColumn = 0;
|
||||
currentRow++;
|
||||
}
|
||||
} else {
|
||||
currentRow++;
|
||||
if (currentRow > qCeil(itemList.size() / columns)) {
|
||||
currentRow = 0;
|
||||
currentColumn++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the preferred size for the layout, considering overlap and orientation.
|
||||
* @return The preferred layout size as a QSize object.
|
||||
*/
|
||||
QSize OverlapLayout::calculatePreferredSize() const
|
||||
{
|
||||
// Get the parent widget for size and margin calculations.
|
||||
const QWidget *parentWidget = this->parentWidget();
|
||||
if (!parentWidget) {
|
||||
return QSize(0, 0);
|
||||
}
|
||||
|
||||
if (itemList.isEmpty()) {
|
||||
return QSize(0, 0);
|
||||
}
|
||||
|
||||
int availableWidth = parentWidget->width();
|
||||
int availableHeight = parentWidget->height();
|
||||
const QMargins margins = parentWidget->contentsMargins();
|
||||
availableWidth -= margins.left() + margins.right();
|
||||
availableHeight -= margins.top() + margins.bottom();
|
||||
|
||||
// Determine the maximum item width and height among all layout items.
|
||||
int maxItemWidth = 0;
|
||||
int maxItemHeight = 0;
|
||||
for (QLayoutItem *item : itemList) {
|
||||
if (item != nullptr && item->widget()) {
|
||||
QSize itemSize = item->widget()->sizeHint();
|
||||
maxItemWidth = qMax(maxItemWidth, itemSize.width());
|
||||
maxItemHeight = qMax(maxItemHeight, itemSize.height());
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the overlap offsets based on the layout direction and overlap percentage.
|
||||
const int overlapOffsetWidth = (overlapDirection == Qt::Horizontal) ? (maxItemWidth * overlapPercentage / 100) : 0;
|
||||
const int overlapOffsetHeight = (overlapDirection == Qt::Vertical) ? (maxItemHeight * overlapPercentage / 100) : 0;
|
||||
|
||||
// Determine the number of columns based on layout constraints and available space.
|
||||
int columns;
|
||||
if (flowDirection == Qt::Horizontal) {
|
||||
if (maxColumns > 0) {
|
||||
// Calculate the maximum possible columns given the available width and overlap.
|
||||
const int availableColumns = (availableWidth + overlapOffsetWidth) / (maxItemWidth - overlapOffsetWidth);
|
||||
// Use the smaller of maxColumns and availableColumns.
|
||||
qCDebug(OverlapLayoutLog) << " Max Columns " << maxColumns << " available columns " << availableColumns;
|
||||
columns = qMin(maxColumns, availableColumns);
|
||||
if (columns < 1) {
|
||||
columns = 1;
|
||||
}
|
||||
} else {
|
||||
// If no maxColumns constraint, allow as many columns as possible.
|
||||
columns = INT_MAX;
|
||||
}
|
||||
} else {
|
||||
// If not a horizontal layout, column count is irrelevant.
|
||||
columns = INT_MAX;
|
||||
}
|
||||
|
||||
// Determine the number of rows based on layout constraints and available space.
|
||||
int rows;
|
||||
if (flowDirection == Qt::Vertical) {
|
||||
if (maxRows > 0) {
|
||||
// Calculate the maximum possible rows given the available height and overlap.
|
||||
const int availableRows = (availableHeight + overlapOffsetHeight) / (maxItemHeight - overlapOffsetHeight);
|
||||
// Use the smaller of maxRows and availableRows.
|
||||
qCDebug(OverlapLayoutLog) << " Max Rows " << maxRows << " available rows " << availableRows;
|
||||
rows = qMin(maxRows, availableRows);
|
||||
if (rows < 1) {
|
||||
rows = 1;
|
||||
}
|
||||
} else {
|
||||
// If no maxRows constraint, allow as many rows as possible.
|
||||
rows = INT_MAX;
|
||||
}
|
||||
} else {
|
||||
// If not a vertical layout, row count is irrelevant.
|
||||
rows = INT_MAX;
|
||||
}
|
||||
|
||||
if (overlapDirection == Qt::Horizontal) {
|
||||
return QSize(maxItemWidth + ((qCeil(itemList.size() / rows)) * (maxItemWidth - overlapOffsetWidth)),
|
||||
rows * maxItemHeight);
|
||||
} else {
|
||||
return QSize(columns * maxItemWidth,
|
||||
maxItemHeight + ((qCeil(itemList.size() / columns)) * (maxItemHeight - overlapOffsetHeight)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the size hint for the layout, based on preferred size calculations.
|
||||
*
|
||||
* Provides a recommended size for the layout, useful for layouts that need to fit within
|
||||
* a specific parent container size. This takes into account the preferred size and
|
||||
* any specific item size requirements.
|
||||
*
|
||||
* @return The layout's recommended QSize.
|
||||
*/
|
||||
QSize OverlapLayout::sizeHint() const
|
||||
{
|
||||
return calculatePreferredSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides the minimum size hint for the layout, ensuring functionality within constraints.
|
||||
*
|
||||
* Defines a minimum workable size for the layout to prevent excessive compression
|
||||
* that could distort item arrangement.
|
||||
*
|
||||
* @return The minimum QSize for this layout.
|
||||
*/
|
||||
QSize OverlapLayout::minimumSize() const
|
||||
{
|
||||
return calculatePreferredSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the layout's orientation direction.
|
||||
*
|
||||
* @param _direction The new orientation direction (Qt::Horizontal or Qt::Vertical).
|
||||
*/
|
||||
void OverlapLayout::setDirection(const Qt::Orientation _direction)
|
||||
{
|
||||
overlapDirection = _direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the maximum number of columns for horizontal orientation.
|
||||
*
|
||||
* @param _maxColumns New maximum column count.
|
||||
*/
|
||||
void OverlapLayout::setMaxColumns(const int _maxColumns)
|
||||
{
|
||||
if (_maxColumns >= 0) {
|
||||
maxColumns = _maxColumns;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the maximum number of rows for vertical orientation.
|
||||
*
|
||||
* @param _maxRows New maximum row count.
|
||||
*/
|
||||
void OverlapLayout::setMaxRows(const int _maxRows)
|
||||
{
|
||||
if (_maxRows >= 0) {
|
||||
maxRows = _maxRows;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the maximum number of columns for a vertical overlap layout based on the current width.
|
||||
*
|
||||
* This function determines the maximum number of columns that can fit within the layout's width
|
||||
* given the overlap percentage and item size, based on the current layout direction.
|
||||
*
|
||||
* @return Maximum number of columns that can fit within the layout width.
|
||||
*/
|
||||
int OverlapLayout::calculateMaxColumns() const
|
||||
{
|
||||
if (overlapDirection != Qt::Vertical || itemList.isEmpty()) {
|
||||
return 1; // Only relevant if the layout direction is vertical
|
||||
}
|
||||
|
||||
// Determine maximum item width
|
||||
int maxItemWidth = 0;
|
||||
for (QLayoutItem *item : itemList) {
|
||||
if (item == nullptr || !item->widget()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->widget()->sizeHint();
|
||||
maxItemWidth = qMax(maxItemWidth, itemSize.width());
|
||||
}
|
||||
|
||||
const int availableWidth = parentWidget() ? parentWidget()->width() : 0;
|
||||
// Determine the maximum number of columns that can fit
|
||||
const int columns = availableWidth / maxItemWidth;
|
||||
|
||||
return qMax(1, columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the maximum number of rows needed for a given number of columns in a vertical overlap layout.
|
||||
*
|
||||
* Determines how many rows are required to arrange all items given the calculated or specified number of columns.
|
||||
*
|
||||
* @param columns The number of columns available.
|
||||
* @return The total number of rows required.
|
||||
*/
|
||||
int OverlapLayout::calculateRowsForColumns(const int columns) const
|
||||
{
|
||||
if (overlapDirection != Qt::Vertical || itemList.isEmpty() || columns <= 0) {
|
||||
return 1; // Only relevant if the layout direction is vertical and there are items
|
||||
}
|
||||
|
||||
const int totalItems = static_cast<int>(itemList.size());
|
||||
|
||||
return qCeil(totalItems / columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the maximum number of rows for a horizontal overlap layout based on the current height.
|
||||
*
|
||||
* This function determines the maximum number of rows that can fit within the layout's height
|
||||
* given the overlap percentage and item size, based on the current layout direction.
|
||||
*
|
||||
* @return Maximum number of rows that can fit within the layout height.
|
||||
*/
|
||||
int OverlapLayout::calculateMaxRows() const
|
||||
{
|
||||
if (overlapDirection != Qt::Horizontal || itemList.isEmpty()) {
|
||||
return 1; // Only relevant if the layout direction is horizontal
|
||||
}
|
||||
|
||||
// Determine maximum item height
|
||||
int maxItemHeight = 0;
|
||||
for (QLayoutItem *item : itemList) {
|
||||
if (item == nullptr || !item->widget()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->widget()->sizeHint();
|
||||
maxItemHeight = qMax(maxItemHeight, itemSize.height());
|
||||
}
|
||||
|
||||
// Calculate the effective height of each item with the overlap applied
|
||||
const int overlapOffsetHeight = (maxItemHeight * (100 - overlapPercentage)) / 100;
|
||||
const int availableHeight = parentWidget() ? parentWidget()->height() : 0;
|
||||
|
||||
// Determine the maximum number of rows that can fit
|
||||
const int rows = availableHeight / overlapOffsetHeight;
|
||||
|
||||
return qMax(1, rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the maximum number of columns needed for a given number of rows in a horizontal overlap layout.
|
||||
*
|
||||
* Determines how many columns are required to arrange all items given the calculated or specified number of rows.
|
||||
*
|
||||
* @param rows The number of rows available.
|
||||
* @return The total number of columns required.
|
||||
*/
|
||||
int OverlapLayout::calculateColumnsForRows(const int rows) const
|
||||
{
|
||||
if (overlapDirection != Qt::Horizontal || itemList.isEmpty() || rows <= 0) {
|
||||
return 1; // Only relevant if the layout direction is horizontal and there are items
|
||||
}
|
||||
|
||||
const int totalItems = static_cast<int>(itemList.size());
|
||||
|
||||
return qCeil(totalItems / rows);
|
||||
}
|
||||
50
cockatrice/src/interface/layouts/overlap_layout.h
Normal file
50
cockatrice/src/interface/layouts/overlap_layout.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef OVERLAP_LAYOUT_H
|
||||
#define OVERLAP_LAYOUT_H
|
||||
|
||||
#include <QLayout>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QWidget>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(OverlapLayoutLog, "overlap_layout");
|
||||
|
||||
class OverlapLayout : public QLayout
|
||||
{
|
||||
public:
|
||||
OverlapLayout(QWidget *parent = nullptr,
|
||||
int overlapPercentage = 10,
|
||||
int maxColumns = 2,
|
||||
int maxRows = 2,
|
||||
Qt::Orientation overlapDirection = Qt::Vertical,
|
||||
Qt::Orientation flowDirection = Qt::Horizontal);
|
||||
~OverlapLayout();
|
||||
void insertWidgetAtIndex(QWidget *toInsert, int index);
|
||||
|
||||
void addItem(QLayoutItem *item) override;
|
||||
int count() const override;
|
||||
QLayoutItem *itemAt(int index) const override;
|
||||
QLayoutItem *takeAt(int index) override;
|
||||
void setGeometry(const QRect &rect) override;
|
||||
QSize minimumSize() const override;
|
||||
QSize sizeHint() const override;
|
||||
void setMaxColumns(int _maxColumns);
|
||||
void setMaxRows(int _maxRows);
|
||||
int calculateMaxColumns() const;
|
||||
int calculateRowsForColumns(int columns) const;
|
||||
int calculateMaxRows() const;
|
||||
int calculateColumnsForRows(int rows) const;
|
||||
void setDirection(Qt::Orientation _direction);
|
||||
|
||||
private:
|
||||
QList<QLayoutItem *> itemList;
|
||||
int overlapPercentage;
|
||||
int maxColumns;
|
||||
int maxRows;
|
||||
Qt::Orientation overlapDirection;
|
||||
Qt::Orientation flowDirection;
|
||||
|
||||
// Calculate the preferred size of the layout
|
||||
QSize calculatePreferredSize() const;
|
||||
};
|
||||
|
||||
#endif // OVERLAP_LAYOUT_H
|
||||
133
cockatrice/src/interface/line_edit_completer.cpp
Normal file
133
cockatrice/src/interface/line_edit_completer.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#include "line_edit_completer.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QCompleter>
|
||||
#include <QFocusEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QStringListModel>
|
||||
#include <QTextCursor>
|
||||
#include <QWidget>
|
||||
|
||||
LineEditCompleter::LineEditCompleter(QWidget *parent) : LineEditUnfocusable(parent), c(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void LineEditCompleter::focusOutEvent(QFocusEvent *e)
|
||||
{
|
||||
LineEditUnfocusable::focusOutEvent(e);
|
||||
if (c->popup()->isVisible()) {
|
||||
// Remove Popup
|
||||
c->popup()->hide();
|
||||
// Truncate the line to last space or whole string
|
||||
QString textValue = text();
|
||||
int lastIndex = textValue.length();
|
||||
int lastWordStartIndex = textValue.lastIndexOf(" ") + 1;
|
||||
int leftShift = qMin(lastIndex, lastWordStartIndex);
|
||||
setText(textValue.left(leftShift));
|
||||
// Insert highlighted line from popup
|
||||
insert(c->completionModel()->index(c->popup()->currentIndex().row(), 0).data().toString() + " ");
|
||||
// Set focus back to the textbox since tab was pressed
|
||||
setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void LineEditCompleter::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Escape:
|
||||
if (c->popup()->isVisible()) {
|
||||
event->ignore();
|
||||
// Remove Popup
|
||||
c->popup()->hide();
|
||||
// Truncate the line to last space or whole string
|
||||
QString textValue = text();
|
||||
int lastIndexof = qMax(0, textValue.lastIndexOf(" "));
|
||||
QString finalString = textValue.left(lastIndexof);
|
||||
// Add a space if there's a word
|
||||
if (finalString != "")
|
||||
finalString += " ";
|
||||
setText(finalString);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case Qt::Key_Space:
|
||||
if (c->popup()->isVisible()) {
|
||||
event->ignore();
|
||||
// Remove Popup
|
||||
c->popup()->hide();
|
||||
// Truncate the line to last space or whole string
|
||||
QString textValue = text();
|
||||
int lastIndex = textValue.length();
|
||||
int lastWordStartIndex = textValue.lastIndexOf(" ") + 1;
|
||||
int leftShift = qMin(lastIndex, lastWordStartIndex);
|
||||
setText(textValue.left(leftShift));
|
||||
// Insert highlighted line from popup
|
||||
insert(c->completionModel()->index(c->popup()->currentIndex().row(), 0).data().toString() + " ");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
LineEditUnfocusable::keyPressEvent(event);
|
||||
// return if the completer is null or if the most recently typed char was '@'.
|
||||
// Only want the popup AFTER typing the first char of the mention.
|
||||
if (!c || text().right(1).contains("@")) {
|
||||
c->popup()->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set new completion prefix
|
||||
c->setCompletionPrefix(cursorWord(text()));
|
||||
if (c->completionPrefix().length() < 1) {
|
||||
c->popup()->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw completion box
|
||||
QRect cr = cursorRect();
|
||||
cr.setWidth(c->popup()->sizeHintForColumn(0) + c->popup()->verticalScrollBar()->sizeHint().width());
|
||||
c->complete(cr);
|
||||
|
||||
// Select first item in the completion popup
|
||||
QItemSelectionModel *sm = new QItemSelectionModel(c->completionModel());
|
||||
c->popup()->setSelectionModel(sm);
|
||||
sm->select(c->completionModel()->index(0, 0), QItemSelectionModel::ClearAndSelect);
|
||||
sm->setCurrentIndex(c->completionModel()->index(0, 0), QItemSelectionModel::NoUpdate);
|
||||
}
|
||||
|
||||
QString LineEditCompleter::cursorWord(const QString &line) const
|
||||
{
|
||||
return line.mid(line.left(cursorPosition()).lastIndexOf(" ") + 1,
|
||||
cursorPosition() - line.left(cursorPosition()).lastIndexOf(" ") - 1);
|
||||
}
|
||||
|
||||
void LineEditCompleter::insertCompletion(QString arg)
|
||||
{
|
||||
QString s_arg = arg + " ";
|
||||
setText(text().replace(text().left(cursorPosition()).lastIndexOf(" ") + 1,
|
||||
cursorPosition() - text().left(cursorPosition()).lastIndexOf(" ") - 1, s_arg));
|
||||
}
|
||||
|
||||
void LineEditCompleter::setCompleter(QCompleter *completer)
|
||||
{
|
||||
c = completer;
|
||||
c->setWidget(this);
|
||||
connect(c, qOverload<const QString &>(&QCompleter::activated), this, &LineEditCompleter::insertCompletion);
|
||||
}
|
||||
|
||||
void LineEditCompleter::setCompletionList(QStringList completionList)
|
||||
{
|
||||
if (!c || c->popup()->isVisible())
|
||||
return;
|
||||
|
||||
QStringListModel *model;
|
||||
model = (QStringListModel *)(c->model());
|
||||
if (model == NULL)
|
||||
model = new QStringListModel();
|
||||
model->setStringList(completionList);
|
||||
}
|
||||
29
cockatrice/src/interface/line_edit_completer.h
Normal file
29
cockatrice/src/interface/line_edit_completer.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef LINEEDITCOMPLETER_H
|
||||
#define LINEEDITCOMPLETER_H
|
||||
|
||||
#include "../deck/custom_line_edit.h"
|
||||
|
||||
#include <QFocusEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QStringList>
|
||||
|
||||
class LineEditCompleter : public LineEditUnfocusable
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QString cursorWord(const QString &line) const;
|
||||
QCompleter *c;
|
||||
private slots:
|
||||
void insertCompletion(QString);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
void focusOutEvent(QFocusEvent *e);
|
||||
|
||||
public:
|
||||
explicit LineEditCompleter(QWidget *parent = nullptr);
|
||||
void setCompleter(QCompleter *);
|
||||
void setCompletionList(QStringList);
|
||||
};
|
||||
|
||||
#endif
|
||||
392
cockatrice/src/interface/pixel_map_generator.cpp
Normal file
392
cockatrice/src/interface/pixel_map_generator.cpp
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
#include "pixel_map_generator.h"
|
||||
|
||||
#include "pb/serverinfo_user.pb.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDomDocument>
|
||||
#include <QFile>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QSvgRenderer>
|
||||
|
||||
#define DEFAULT_COLOR_UNREGISTERED "#32c8ec";
|
||||
#define DEFAULT_COLOR_REGISTERED "#5ed900";
|
||||
#define DEFAULT_COLOR_MODERATOR_LEFT "#ffffff";
|
||||
#define DEFAULT_COLOR_MODERATOR_RIGHT "#000000";
|
||||
#define DEFAULT_COLOR_ADMIN "#ff2701";
|
||||
|
||||
/**
|
||||
* Loads in an svg from file and scales it without affecting image quality.
|
||||
*
|
||||
* @param svgPath The path to the svg file, with file extension.
|
||||
* @param size The desired size of the pixmap.
|
||||
* @param expandOnly If true, then keep the size of the initial pixmap to at least the svg size.
|
||||
*
|
||||
* @return The svg loaded into a Pixmap with the given size, or an empty Pixmap if the loading failed.
|
||||
*/
|
||||
static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnly = false)
|
||||
{
|
||||
QSvgRenderer svgRenderer(svgPath);
|
||||
|
||||
if (!svgRenderer.isValid()) {
|
||||
qCWarning(PixelMapGeneratorLog) << "Failed to load" << svgPath;
|
||||
return {};
|
||||
}
|
||||
|
||||
// If expandOnly, make sure the pixmap is at least as large as the svg, so that we don't lose any detail.
|
||||
// QIcon.pixmap(size) will automatically scale down the image, but it won't scale it up.
|
||||
QSize pixmapSize = expandOnly ? svgRenderer.defaultSize().expandedTo(size) : size;
|
||||
QPixmap pix(pixmapSize);
|
||||
pix.fill(Qt::transparent);
|
||||
|
||||
QPainter pixPainter(&pix);
|
||||
svgRenderer.render(&pixPainter);
|
||||
|
||||
// Converting the pixmap to a QIcon and back is the easiest way to scale down a svg without affecting image quality
|
||||
if (expandOnly) {
|
||||
return QIcon(pix).pixmap(size);
|
||||
}
|
||||
|
||||
return pix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to load path image from non-SVG formats, otherwise fall back to SVG.
|
||||
* This is to allow custom themes to support non-SVG format type overrides, since SVG requires custom loading.
|
||||
* @param path The path to the file, with no file extension. File formats will be automatically detected.
|
||||
* @param size The desired size of the pixmap.
|
||||
* @param expandOnly If true, then keep the size of the initial pixmap to at least the size (Only relevant if SVG).
|
||||
*
|
||||
* @return The loaded image into a Pixmap with the given size, or an empty Pixmap if the loading failed.
|
||||
*/
|
||||
static QPixmap tryLoadImage(const QString &path, const QSize &size, bool expandOnly = false)
|
||||
{
|
||||
const auto formats = {"png", "jpg"};
|
||||
|
||||
QPixmap returnPixmap;
|
||||
for (const auto &format : formats) {
|
||||
if (returnPixmap.load(path, format)) {
|
||||
return returnPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
}
|
||||
|
||||
return loadSvg(path + ".svg", size, expandOnly);
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> PhasePixmapGenerator::pmCache;
|
||||
|
||||
QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name)
|
||||
{
|
||||
QString key = name + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QPixmap pixmap = tryLoadImage("theme:phases/" + name, QSize(height, height));
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> CounterPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap CounterPixmapGenerator::generatePixmap(int height, QString name, bool highlight)
|
||||
{
|
||||
// The colorless counter is named "x" by the server but the file is named "general.svg"
|
||||
if (name == "x") {
|
||||
name = "general";
|
||||
}
|
||||
|
||||
if (highlight)
|
||||
name.append("_highlight");
|
||||
QString key = name + QString::number(height);
|
||||
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)
|
||||
name.append("_highlight");
|
||||
pixmap = tryLoadImage("theme:counters/" + name, QSize(height, height));
|
||||
}
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QPixmap PingPixmapGenerator::generatePixmap(int size, int value, int max)
|
||||
{
|
||||
int key = size * 1000000 + max * 1000 + value;
|
||||
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))
|
||||
color = Qt::black;
|
||||
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);
|
||||
g.setColorAt(0, color);
|
||||
g.setColorAt(1, Qt::transparent);
|
||||
painter.fillRect(0, 0, pixmap.width(), pixmap.height(), QBrush(g));
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<int, QPixmap> PingPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap CountryPixmapGenerator::generatePixmap(int height, const QString &countryCode)
|
||||
{
|
||||
if (countryCode.size() != 2)
|
||||
return QPixmap();
|
||||
QString key = countryCode + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
int width = height * 2;
|
||||
QPixmap pixmap = tryLoadImage("theme:countries/" + countryCode.toLower(), QSize(width, height), true);
|
||||
|
||||
QPainter painter(&pixmap);
|
||||
painter.setPen(Qt::black);
|
||||
|
||||
// width/height offset was determined through trial-and-error
|
||||
#ifdef Q_OS_MACOS
|
||||
painter.drawRect(0, 0, pixmap.width() / 2, pixmap.height() / 2);
|
||||
#else
|
||||
painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1);
|
||||
#endif
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> CountryPixmapGenerator::pmCache;
|
||||
|
||||
/**
|
||||
* Updates tags in the svg
|
||||
*
|
||||
* @param elem The svg
|
||||
* @param tagName tag with attribute to update
|
||||
* @param attrName attribute to be updated
|
||||
* @param idName id that the tag has to match
|
||||
* @param attrValue the value to update the attribute to
|
||||
*/
|
||||
static void setAttrRecur(QDomElement &elem,
|
||||
const QString &tagName,
|
||||
const QString &attrName,
|
||||
const QString &idName,
|
||||
const QString &attrValue);
|
||||
|
||||
void setAttrRecur(QDomElement &elem,
|
||||
const QString &tagName,
|
||||
const QString &attrName,
|
||||
const QString &idName,
|
||||
const QString &attrValue)
|
||||
{
|
||||
if (elem.tagName().compare(tagName) == 0) {
|
||||
if (elem.attribute("id").compare(idName) == 0) {
|
||||
elem.setAttribute(attrName, attrValue);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < elem.childNodes().count(); i++) {
|
||||
if (!elem.childNodes().at(i).isElement()) {
|
||||
continue;
|
||||
}
|
||||
auto docElem = elem.childNodes().at(i).toElement();
|
||||
setAttrRecur(docElem, tagName, attrName, idName, attrValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the usericon svg and fills in its colors.
|
||||
* The image is kept as a QIcon to preserve the image quality.
|
||||
*
|
||||
* Call icon.pixmap(w, h) in order to convert this icon into a pixmap with the given dimensions.
|
||||
* Avoid scaling the pixmap in other ways, as that destroys image quality.
|
||||
*
|
||||
* @param minSize If the dimensions of the source svg is smaller than this, then it will be scaled up to this size
|
||||
*/
|
||||
static QIcon loadAndColorSvg(const QString &iconPath,
|
||||
const QString &colorLeft,
|
||||
const std::optional<QString> &colorRight,
|
||||
const int minSize)
|
||||
{
|
||||
QFile file(iconPath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCWarning(PixelMapGeneratorLog) << "Unable to open" << iconPath;
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto &baData = file.readAll();
|
||||
QDomDocument doc;
|
||||
doc.setContent(baData);
|
||||
|
||||
auto docElem = doc.documentElement();
|
||||
|
||||
setAttrRecur(docElem, "path", "fill", "left", colorLeft);
|
||||
if (colorRight.has_value()) {
|
||||
setAttrRecur(docElem, "path", "fill", "right", colorRight.value());
|
||||
}
|
||||
|
||||
QSvgRenderer svgRenderer(doc.toByteArray());
|
||||
|
||||
QPixmap pix(svgRenderer.defaultSize().expandedTo(QSize(minSize, minSize)));
|
||||
pix.fill(Qt::transparent);
|
||||
|
||||
QPainter pixPainter(&pix);
|
||||
svgRenderer.render(&pixPainter);
|
||||
|
||||
return QIcon(pix);
|
||||
}
|
||||
|
||||
QPixmap UserLevelPixmapGenerator::generatePixmap(int height,
|
||||
UserLevelFlags userLevel,
|
||||
ServerInfo_User::PawnColorsOverride pawnColorsOverride,
|
||||
bool isBuddy,
|
||||
const QString &privLevel)
|
||||
{
|
||||
return generateIcon(height, userLevel, pawnColorsOverride, isBuddy, privLevel).pixmap(height, height);
|
||||
}
|
||||
|
||||
QIcon UserLevelPixmapGenerator::generateIcon(int minHeight,
|
||||
UserLevelFlags userLevel,
|
||||
ServerInfo_User::PawnColorsOverride pawnColorsOverride,
|
||||
bool isBuddy,
|
||||
const QString &privLevel)
|
||||
{
|
||||
std::optional<QString> colorLeft = std::nullopt;
|
||||
if (pawnColorsOverride.has_left_side()) {
|
||||
colorLeft = QString::fromStdString(pawnColorsOverride.left_side());
|
||||
}
|
||||
|
||||
std::optional<QString> colorRight = std::nullopt;
|
||||
if (pawnColorsOverride.has_right_side()) {
|
||||
colorRight = QString::fromStdString(pawnColorsOverride.right_side());
|
||||
}
|
||||
|
||||
QString key = QString::number(minHeight * 10000) + ":" + static_cast<short>(userLevel) + ":" +
|
||||
static_cast<short>(isBuddy) + ":" + privLevel.toLower() + ":" + colorLeft.value_or("") + ":" +
|
||||
colorRight.value_or("");
|
||||
|
||||
if (iconCache.contains(key)) {
|
||||
return iconCache.value(key);
|
||||
}
|
||||
|
||||
QIcon icon = colorLeft.has_value()
|
||||
? generateIconWithColorOverride(minHeight, isBuddy, userLevel, privLevel, colorLeft, colorRight)
|
||||
: generateIconDefault(minHeight, userLevel, isBuddy, privLevel);
|
||||
|
||||
iconCache.insert(key, icon);
|
||||
return icon;
|
||||
}
|
||||
|
||||
static QString getIconType(const bool isBuddy, const UserLevelFlags &userLevelFlags, const QString &privLevel)
|
||||
{
|
||||
if (isBuddy) {
|
||||
return "star";
|
||||
}
|
||||
|
||||
if (userLevelFlags.testFlag(ServerInfo_User_UserLevelFlag_IsJudge)) {
|
||||
return "pawn_judge";
|
||||
}
|
||||
|
||||
if (!privLevel.isEmpty() && privLevel.toLower() != "none") {
|
||||
return QString("pawn_%1").arg(privLevel.toLower());
|
||||
}
|
||||
|
||||
return "pawn";
|
||||
}
|
||||
|
||||
QIcon UserLevelPixmapGenerator::generateIconDefault(int height,
|
||||
UserLevelFlags userLevel,
|
||||
bool isBuddy,
|
||||
const QString &privLevel)
|
||||
{
|
||||
const auto &iconType = getIconType(isBuddy, userLevel, privLevel);
|
||||
|
||||
QString arity = "single";
|
||||
QString colorLeft;
|
||||
std::optional<QString> colorRight = std::nullopt;
|
||||
|
||||
if (userLevel.testFlag(ServerInfo_User::IsAdmin)) {
|
||||
colorLeft = DEFAULT_COLOR_ADMIN;
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsModerator)) {
|
||||
colorLeft = DEFAULT_COLOR_MODERATOR_LEFT;
|
||||
colorRight = DEFAULT_COLOR_MODERATOR_RIGHT;
|
||||
arity = "double";
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) {
|
||||
colorLeft = DEFAULT_COLOR_REGISTERED;
|
||||
} else {
|
||||
colorLeft = DEFAULT_COLOR_UNREGISTERED;
|
||||
}
|
||||
|
||||
const auto &iconPath = QString("theme:usericons/%1_%2.svg").arg(iconType, arity);
|
||||
return loadAndColorSvg(iconPath, colorLeft, colorRight, height);
|
||||
}
|
||||
|
||||
QIcon UserLevelPixmapGenerator::generateIconWithColorOverride(int height,
|
||||
bool isBuddy,
|
||||
const UserLevelFlags &userLevelFlags,
|
||||
const QString &privLevel,
|
||||
const std::optional<QString> &colorLeft,
|
||||
const std::optional<QString> &colorRight)
|
||||
{
|
||||
const auto &iconType = getIconType(isBuddy, userLevelFlags, privLevel);
|
||||
const QString &arity = colorRight.has_value() ? "double" : "single";
|
||||
const auto &iconPath = QString("theme:usericons/%1_%2.svg").arg(iconType, arity);
|
||||
return loadAndColorSvg(iconPath, colorLeft.value(), colorRight, height);
|
||||
}
|
||||
|
||||
QMap<QString, QIcon> UserLevelPixmapGenerator::iconCache;
|
||||
|
||||
QPixmap LockPixmapGenerator::generatePixmap(int height)
|
||||
{
|
||||
|
||||
int key = height;
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QPixmap pixmap = tryLoadImage("theme:icons/lock", QSize(height, height), true);
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<int, QPixmap> LockPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
|
||||
{
|
||||
QString key = QString::number(expanded) + ":" + QString::number(height);
|
||||
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);
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> DropdownIconPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap loadColorAdjustedPixmap(const QString &name)
|
||||
{
|
||||
if (qApp->palette().windowText().color().lightness() > 200) {
|
||||
QImage img(name);
|
||||
img.invertPixels();
|
||||
QPixmap result;
|
||||
result.convertFromImage(img);
|
||||
return result;
|
||||
} else {
|
||||
return QPixmap(name);
|
||||
}
|
||||
}
|
||||
124
cockatrice/src/interface/pixel_map_generator.h
Normal file
124
cockatrice/src/interface/pixel_map_generator.h
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#ifndef PIXMAPGENERATOR_H
|
||||
#define PIXMAPGENERATOR_H
|
||||
|
||||
#include "user_level.h"
|
||||
|
||||
#include <QIcon>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMap>
|
||||
#include <QPixmap>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(PixelMapGeneratorLog, "pixel_map_generator");
|
||||
|
||||
class PhasePixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<QString, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int size, QString name);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class CounterPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<QString, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int size, QString name, bool highlight);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class PingPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<int, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int size, int value, int max);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class CountryPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<QString, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int height, const QString &countryCode);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class UserLevelPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<QString, QIcon> iconCache;
|
||||
|
||||
static QIcon generateIconDefault(int height, UserLevelFlags userLevel, bool isBuddy, const QString &privLevel);
|
||||
static QIcon generateIconWithColorOverride(int height,
|
||||
bool isBuddy,
|
||||
const UserLevelFlags &userLevelFlags,
|
||||
const QString &privLevel,
|
||||
const std::optional<QString> &colorLeft,
|
||||
const std::optional<QString> &colorRight);
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int height,
|
||||
UserLevelFlags userLevel,
|
||||
ServerInfo_User::PawnColorsOverride pawnColors,
|
||||
bool isBuddy,
|
||||
const QString &privLevel);
|
||||
|
||||
static QIcon generateIcon(int minHeight,
|
||||
UserLevelFlags userLevel,
|
||||
ServerInfo_User::PawnColorsOverride pawnColors,
|
||||
bool isBuddy,
|
||||
const QString &privLevel);
|
||||
static void clear()
|
||||
{
|
||||
iconCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class LockPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<int, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int height);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class DropdownIconPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<QString, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int height, bool expanded);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
QPixmap loadColorAdjustedPixmap(const QString &name);
|
||||
|
||||
#endif
|
||||
30
cockatrice/src/interface/tearoff_menu.h
Normal file
30
cockatrice/src/interface/tearoff_menu.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include "../settings/cache_settings.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
class TearOffMenu : public QMenu
|
||||
{
|
||||
public:
|
||||
explicit TearOffMenu(const QString &title, QWidget *parent = nullptr) : QMenu(title, parent)
|
||||
{
|
||||
connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this,
|
||||
[this](const bool state) { setTearOffEnabled(state); });
|
||||
setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus());
|
||||
}
|
||||
|
||||
explicit TearOffMenu(QWidget *parent = nullptr) : QMenu(parent)
|
||||
{
|
||||
connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this,
|
||||
[this](const bool state) { setTearOffEnabled(state); });
|
||||
setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus());
|
||||
}
|
||||
|
||||
TearOffMenu *addTearOffMenu(const QString &title)
|
||||
{
|
||||
auto *menu = new TearOffMenu(title, this);
|
||||
addMenu(menu);
|
||||
return menu;
|
||||
}
|
||||
};
|
||||
189
cockatrice/src/interface/theme_manager.cpp
Normal file
189
cockatrice/src/interface/theme_manager.cpp
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
#include "theme_manager.h"
|
||||
|
||||
#include "../settings/cache_settings.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QColor>
|
||||
#include <QDebug>
|
||||
#include <QLibraryInfo>
|
||||
#include <QPixmapCache>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#define NONE_THEME_NAME "Default"
|
||||
#define STYLE_CSS_NAME "style.css"
|
||||
#define HANDZONE_BG_NAME "handzone"
|
||||
#define PLAYERZONE_BG_NAME "playerzone"
|
||||
#define STACKZONE_BG_NAME "stackzone"
|
||||
#define TABLEZONE_BG_NAME "tablezone"
|
||||
static const QColor HANDZONE_BG_DEFAULT = QColor(80, 100, 50);
|
||||
static const QColor TABLEZONE_BG_DEFAULT = QColor(70, 50, 100);
|
||||
static const QColor PLAYERZONE_BG_DEFAULT = QColor(200, 200, 200);
|
||||
static const QColor STACKZONE_BG_DEFAULT = QColor(113, 43, 43);
|
||||
static const QStringList DEFAULT_RESOURCE_PATHS = {":/resources"};
|
||||
|
||||
ThemeManager::ThemeManager(QObject *parent) : QObject(parent)
|
||||
{
|
||||
ensureThemeDirectoryExists();
|
||||
connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, &ThemeManager::themeChangedSlot);
|
||||
themeChangedSlot();
|
||||
}
|
||||
|
||||
void ThemeManager::ensureThemeDirectoryExists()
|
||||
{
|
||||
if (SettingsCache::instance().getThemeName().isEmpty() ||
|
||||
!getAvailableThemes().contains(SettingsCache::instance().getThemeName())) {
|
||||
qCInfo(ThemeManagerLog) << "Theme name not set, setting default value";
|
||||
SettingsCache::instance().setThemeName(NONE_THEME_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
if (!availableThemes.contains(themeName)) {
|
||||
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
|
||||
}
|
||||
}
|
||||
|
||||
// load themes from cockatrice system dir
|
||||
dir.setPath(qApp->applicationDirPath() +
|
||||
#ifdef Q_OS_MAC
|
||||
"/../Resources/themes"
|
||||
#elif defined(Q_OS_WIN)
|
||||
"/themes"
|
||||
#else // linux
|
||||
"/../share/cockatrice/themes"
|
||||
#endif
|
||||
);
|
||||
|
||||
for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
if (!availableThemes.contains(themeName)) {
|
||||
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
|
||||
}
|
||||
}
|
||||
|
||||
return availableThemes;
|
||||
}
|
||||
|
||||
QBrush ThemeManager::loadBrush(QString fileName, QColor fallbackColor)
|
||||
{
|
||||
QBrush brush;
|
||||
QPixmap tmp = QPixmap("theme:zones/" + fileName);
|
||||
if (tmp.isNull()) {
|
||||
brush.setColor(fallbackColor);
|
||||
brush.setStyle(Qt::SolidPattern);
|
||||
} else {
|
||||
brush.setTexture(tmp);
|
||||
}
|
||||
|
||||
return brush;
|
||||
}
|
||||
|
||||
QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush)
|
||||
{
|
||||
QBrush brush;
|
||||
QPixmap tmp = QPixmap("theme:zones/" + fileName);
|
||||
|
||||
if (tmp.isNull()) {
|
||||
brush = fallbackBrush;
|
||||
} else {
|
||||
brush.setTexture(tmp);
|
||||
}
|
||||
|
||||
return brush;
|
||||
}
|
||||
|
||||
void ThemeManager::themeChangedSlot()
|
||||
{
|
||||
QString themeName = SettingsCache::instance().getThemeName();
|
||||
qCInfo(ThemeManagerLog) << "Theme changed:" << themeName;
|
||||
|
||||
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("");
|
||||
}
|
||||
|
||||
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;
|
||||
} 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);
|
||||
}
|
||||
for (auto &brushCache : brushesCache) {
|
||||
brushCache.clear();
|
||||
}
|
||||
|
||||
QPixmapCache::clear();
|
||||
|
||||
emit themeChanged();
|
||||
}
|
||||
|
||||
static QString roleBgName(ThemeManager::Role role)
|
||||
{
|
||||
switch (role) {
|
||||
case ThemeManager::Hand:
|
||||
return HANDZONE_BG_NAME;
|
||||
|
||||
case ThemeManager::Player:
|
||||
return PLAYERZONE_BG_NAME;
|
||||
|
||||
case ThemeManager::Stack:
|
||||
return STACKZONE_BG_NAME;
|
||||
|
||||
case ThemeManager::Table:
|
||||
return TABLEZONE_BG_NAME;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
QBrush &ThemeManager::getBgBrush(Role role)
|
||||
{
|
||||
return brushes[role];
|
||||
}
|
||||
|
||||
QBrush ThemeManager::getExtraBgBrush(Role role, int zoneId)
|
||||
{
|
||||
if (zoneId <= 0) {
|
||||
return getBgBrush(role);
|
||||
}
|
||||
|
||||
QBrushMap &brushCache = brushesCache[role];
|
||||
|
||||
if (!brushCache.contains(zoneId)) {
|
||||
QBrush brush = loadExtraBrush(roleBgName(role) + QString::number(zoneId), getBgBrush(role));
|
||||
brushCache.insert(zoneId, brush);
|
||||
return brush;
|
||||
}
|
||||
|
||||
return brushCache.value(zoneId);
|
||||
}
|
||||
62
cockatrice/src/interface/theme_manager.h
Normal file
62
cockatrice/src/interface/theme_manager.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#ifndef THEMEMANAGER_H
|
||||
#define THEMEMANAGER_H
|
||||
|
||||
#include <QBrush>
|
||||
#include <QDir>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QPixmap>
|
||||
#include <QString>
|
||||
#include <array>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(ThemeManagerLog, "theme_manager");
|
||||
|
||||
typedef QMap<QString, QString> QStringMap;
|
||||
typedef QMap<int, QBrush> QBrushMap;
|
||||
|
||||
class QApplication;
|
||||
|
||||
class ThemeManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ThemeManager(QObject *parent = nullptr);
|
||||
|
||||
enum Role
|
||||
{
|
||||
MinRole = 0,
|
||||
Hand = MinRole,
|
||||
Stack,
|
||||
Table,
|
||||
Player,
|
||||
MaxRole = Player,
|
||||
};
|
||||
|
||||
private:
|
||||
std::array<QBrush, Role::MaxRole + 1> brushes;
|
||||
QStringMap availableThemes;
|
||||
/*
|
||||
Internal cache for multiple backgrounds
|
||||
*/
|
||||
std::array<QBrushMap, Role::MaxRole + 1> brushesCache;
|
||||
|
||||
protected:
|
||||
void ensureThemeDirectoryExists();
|
||||
QBrush loadBrush(QString fileName, QColor fallbackColor);
|
||||
QBrush loadExtraBrush(QString fileName, QBrush &fallbackBrush);
|
||||
|
||||
public:
|
||||
QStringMap &getAvailableThemes();
|
||||
|
||||
QBrush &getBgBrush(Role zone);
|
||||
QBrush getExtraBgBrush(Role zone, int zoneId = 0);
|
||||
protected slots:
|
||||
void themeChangedSlot();
|
||||
signals:
|
||||
void themeChanged();
|
||||
};
|
||||
|
||||
extern ThemeManager *themeManager;
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
#include "color_identity_widget.h"
|
||||
|
||||
#include "../../../../settings/cache_settings.h"
|
||||
#include "mana_symbol_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
#include <QRegularExpression>
|
||||
#include <QResizeEvent>
|
||||
#include <QSize>
|
||||
|
||||
ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, CardInfoPtr _card) : QWidget(parent), card(_card)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setSpacing(5); // Small spacing between icons
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setAlignment(Qt::AlignCenter); // Ensure icons are centered
|
||||
setLayout(layout);
|
||||
|
||||
// Define the full WUBRG set (White, Blue, Black, Red, Green)
|
||||
QString fullColorIdentity = "WUBRG";
|
||||
|
||||
if (card) {
|
||||
manaCost = card->getColors(); // Get mana cost string
|
||||
QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string
|
||||
|
||||
populateManaSymbolWidgets();
|
||||
}
|
||||
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageDrawUnusedColorIdentitiesChanged, this,
|
||||
&ColorIdentityWidget::toggleUnusedVisibility);
|
||||
}
|
||||
|
||||
ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString _manaCost)
|
||||
: QWidget(parent), card(nullptr), manaCost(_manaCost)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setSpacing(5); // Small spacing between icons
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setAlignment(Qt::AlignCenter); // Ensure icons are centered
|
||||
setLayout(layout);
|
||||
|
||||
populateManaSymbolWidgets();
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageDrawUnusedColorIdentitiesChanged, this,
|
||||
&ColorIdentityWidget::toggleUnusedVisibility);
|
||||
}
|
||||
|
||||
void ColorIdentityWidget::populateManaSymbolWidgets()
|
||||
{
|
||||
// Define the full WUBRG set (White, Blue, Black, Red, Green)
|
||||
QString fullColorIdentity = "WUBRG";
|
||||
QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string
|
||||
|
||||
if (SettingsCache::instance().getVisualDeckStorageDrawUnusedColorIdentities()) {
|
||||
for (const QString symbol : fullColorIdentity) {
|
||||
auto *manaSymbol = new ManaSymbolWidget(this, symbol, symbols.contains(symbol));
|
||||
layout->addWidget(manaSymbol);
|
||||
}
|
||||
} else {
|
||||
for (const QString &symbol : symbols) {
|
||||
auto *manaSymbol = new ManaSymbolWidget(this, symbol, symbols.contains(symbol));
|
||||
layout->addWidget(manaSymbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ColorIdentityWidget::toggleUnusedVisibility()
|
||||
{
|
||||
if (layout != nullptr) {
|
||||
QLayoutItem *item;
|
||||
while ((item = layout->takeAt(0)) != nullptr) {
|
||||
item->widget()->deleteLater(); // Delete the widget
|
||||
delete item; // Delete the layout item
|
||||
}
|
||||
}
|
||||
populateManaSymbolWidgets();
|
||||
}
|
||||
|
||||
void ColorIdentityWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
QList<ManaSymbolWidget *> manaSymbols = findChildren<ManaSymbolWidget *>();
|
||||
|
||||
if (!manaSymbols.isEmpty()) {
|
||||
int totalWidth = event->size().width();
|
||||
int totalHeight = totalWidth / 6; // Set height to 1/4 of the width
|
||||
setFixedHeight(totalHeight);
|
||||
|
||||
int spacing = layout->spacing();
|
||||
int count = manaSymbols.size();
|
||||
int availableWidth = totalWidth - (spacing * (count - 1));
|
||||
int iconSize = qMin(availableWidth / count, totalHeight); // Ensure icons fit within the new height
|
||||
|
||||
for (ManaSymbolWidget *manaSymbol : manaSymbols) {
|
||||
manaSymbol->setFixedSize(iconSize, iconSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList ColorIdentityWidget::parseColorIdentity(const QString &cmc)
|
||||
{
|
||||
QStringList symbols;
|
||||
|
||||
// Handle split costs (e.g., "3U // 4UU")
|
||||
QStringList splitCosts = cmc.split(" // ");
|
||||
for (const QString &part : splitCosts) {
|
||||
QRegularExpression regex(R"(\{([^}]+)\}|(\d+)|([WUBRGCSPX]))");
|
||||
QRegularExpressionMatchIterator matches = regex.globalMatch(part);
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
if (match.captured(1).isEmpty()) { // If no `{}` group was captured, check other groups
|
||||
if (!match.captured(2).isEmpty()) {
|
||||
symbols.append(match.captured(2)); // Number match
|
||||
} else {
|
||||
symbols.append(match.captured(3)); // Single mana letter match
|
||||
}
|
||||
} else {
|
||||
symbols.append(match.captured(1)); // `{}` enclosed match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef COLOR_IDENTITY_WIDGET_H
|
||||
#define COLOR_IDENTITY_WIDGET_H
|
||||
|
||||
#include "../../../../card/card_info.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class ColorIdentityWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ColorIdentityWidget(QWidget *parent, CardInfoPtr card);
|
||||
explicit ColorIdentityWidget(QWidget *parent, QString manaCost);
|
||||
void populateManaSymbolWidgets();
|
||||
|
||||
QStringList parseColorIdentity(const QString &manaString);
|
||||
|
||||
public slots:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void toggleUnusedVisibility();
|
||||
|
||||
private:
|
||||
CardInfoPtr card;
|
||||
QString manaCost;
|
||||
QHBoxLayout *layout;
|
||||
};
|
||||
|
||||
#endif // COLOR_IDENTITY_WIDGET_H
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
#include "mana_cost_widget.h"
|
||||
|
||||
#include "mana_symbol_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
#include <QResizeEvent>
|
||||
#include <QSize>
|
||||
#include <qregularexpression.h>
|
||||
|
||||
ManaCostWidget::ManaCostWidget(QWidget *parent, CardInfoPtr _card) : QWidget(parent), card(_card)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setSpacing(5); // Small spacing between icons
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(layout);
|
||||
|
||||
setFixedHeight(50); // Fixed height
|
||||
|
||||
if (card) {
|
||||
QString manaCost = card->getManaCost(); // Get mana cost string
|
||||
QStringList symbols = parseManaCost(manaCost); // Parse mana cost string
|
||||
|
||||
for (const QString &symbol : symbols) {
|
||||
ManaSymbolWidget *manaSymbol = new ManaSymbolWidget(this, symbol, true, false);
|
||||
layout->addWidget(manaSymbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ManaCostWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
QList<ManaSymbolWidget *> manaSymbols = findChildren<ManaSymbolWidget *>();
|
||||
|
||||
if (!manaSymbols.isEmpty()) {
|
||||
int totalWidth = event->size().width();
|
||||
int spacing = layout->spacing();
|
||||
int count = manaSymbols.size();
|
||||
|
||||
// Available width minus total spacing
|
||||
int availableWidth = totalWidth - (spacing * (count - 1));
|
||||
int iconSize = qMin(50, availableWidth / count);
|
||||
|
||||
for (ManaSymbolWidget *manaSymbol : manaSymbols) {
|
||||
manaSymbol->setFixedSize(iconSize, iconSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList ManaCostWidget::parseManaCost(const QString &cmc)
|
||||
{
|
||||
QStringList symbols;
|
||||
|
||||
// Handle split costs (e.g., "3U // 4UU")
|
||||
QStringList splitCosts = cmc.split(" // ");
|
||||
for (const QString &part : splitCosts) {
|
||||
QRegularExpression regex(R"(\{([^}]+)\}|(\d+)|([WUBRGCSPX]))");
|
||||
QRegularExpressionMatchIterator matches = regex.globalMatch(part);
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
if (match.captured(1).isEmpty()) { // If no `{}` group was captured, check other groups
|
||||
if (!match.captured(2).isEmpty()) {
|
||||
symbols.append(match.captured(2)); // Number match
|
||||
} else {
|
||||
symbols.append(match.captured(3)); // Single mana letter match
|
||||
}
|
||||
} else {
|
||||
symbols.append(match.captured(1)); // `{}` enclosed match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef MANA_COST_WIDGET_H
|
||||
#define MANA_COST_WIDGET_H
|
||||
|
||||
#include "../../../../card/card_info.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class ManaCostWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ManaCostWidget(QWidget *parent, CardInfoPtr card);
|
||||
|
||||
QStringList parseManaCost(const QString &manaString);
|
||||
public slots:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
CardInfoPtr card;
|
||||
QHBoxLayout *layout;
|
||||
};
|
||||
|
||||
#endif // MANA_COST_WIDGET_H
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
#include "mana_symbol_widget.h"
|
||||
|
||||
#include "../../../../settings/cache_settings.h"
|
||||
|
||||
#include <QResizeEvent>
|
||||
|
||||
ManaSymbolWidget::ManaSymbolWidget(QWidget *parent, QString _symbol, bool _isActive, bool _mayBeToggled)
|
||||
: QLabel(parent), symbol(_symbol), isActive(_isActive), mayBeToggled(_mayBeToggled)
|
||||
{
|
||||
loadManaIcon();
|
||||
setPixmap(manaIcon.scaled(50, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
setMaximumWidth(50);
|
||||
|
||||
// Initialize opacity effect
|
||||
opacityEffect = new QGraphicsOpacityEffect(this);
|
||||
setGraphicsEffect(opacityEffect);
|
||||
updateOpacity();
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageUnusedColorIdentitiesOpacityChanged, this,
|
||||
&ManaSymbolWidget::updateOpacity);
|
||||
}
|
||||
|
||||
void ManaSymbolWidget::toggleSymbol()
|
||||
{
|
||||
setColorActive(!isActive);
|
||||
emit colorToggled(getSymbolChar(), isActive);
|
||||
}
|
||||
|
||||
void ManaSymbolWidget::setColorActive(bool active)
|
||||
{
|
||||
if (isActive != active) {
|
||||
isActive = active;
|
||||
updateOpacity();
|
||||
}
|
||||
}
|
||||
|
||||
void ManaSymbolWidget::updateOpacity()
|
||||
{
|
||||
qreal opacity;
|
||||
if (mayBeToggled) {
|
||||
// UI elements that users can click on shouldn't be transparent.
|
||||
opacity = isActive ? 1.0 : 0.5;
|
||||
} else {
|
||||
// It's just for display, they can do whatever they want.
|
||||
opacity = isActive ? 1.0 : SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity() / 100.0;
|
||||
}
|
||||
opacityEffect->setOpacity(opacity);
|
||||
}
|
||||
|
||||
void ManaSymbolWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
if (mayBeToggled) {
|
||||
toggleSymbol();
|
||||
}
|
||||
}
|
||||
|
||||
void ManaSymbolWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QLabel::resizeEvent(event);
|
||||
setPixmap(manaIcon.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
}
|
||||
|
||||
void ManaSymbolWidget::loadManaIcon()
|
||||
{
|
||||
QString filename = "theme:icons/mana/";
|
||||
|
||||
if (symbol == "W" || symbol == "U" || symbol == "B" || symbol == "R" || symbol == "G") {
|
||||
filename += symbol;
|
||||
}
|
||||
|
||||
manaIcon = QPixmap(filename);
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
#ifndef MANA_SYMBOL_WIDGET_H
|
||||
#define MANA_SYMBOL_WIDGET_H
|
||||
|
||||
#include <QGraphicsOpacityEffect>
|
||||
#include <QLabel>
|
||||
|
||||
class ManaSymbolWidget : public QLabel
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ManaSymbolWidget(QWidget *parent, QString symbol, bool isActive = true, bool mayBeToggled = false);
|
||||
void toggleSymbol();
|
||||
void setColorActive(bool active);
|
||||
void updateOpacity();
|
||||
bool isColorActive() const
|
||||
{
|
||||
return isActive;
|
||||
};
|
||||
QString getSymbol() const
|
||||
{
|
||||
return symbol;
|
||||
};
|
||||
QChar getSymbolChar() const
|
||||
{
|
||||
return symbol[0];
|
||||
};
|
||||
|
||||
void loadManaIcon();
|
||||
|
||||
public slots:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
signals:
|
||||
void colorToggled(QChar symbol, bool isActive);
|
||||
|
||||
private:
|
||||
QString symbol;
|
||||
QPixmap manaIcon;
|
||||
bool isActive;
|
||||
bool mayBeToggled;
|
||||
QGraphicsOpacityEffect *opacityEffect;
|
||||
};
|
||||
|
||||
#endif // MANA_SYMBOL_WIDGET_H
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
#include "card_group_display_widget.h"
|
||||
|
||||
#include "../../../../database/card_database_manager.h"
|
||||
#include "../../../../deck/deck_list_model.h"
|
||||
#include "../../../../utility/card_info_comparator.h"
|
||||
#include "../../../../utility/deck_list_sort_filter_proxy_model.h"
|
||||
#include "../card_info_picture_with_text_overlay_widget.h"
|
||||
|
||||
#include <QResizeEvent>
|
||||
|
||||
CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent,
|
||||
DeckListModel *_deckListModel,
|
||||
QPersistentModelIndex _trackedIndex,
|
||||
QString _zoneName,
|
||||
QString _cardGroupCategory,
|
||||
QString _activeGroupCriteria,
|
||||
QStringList _activeSortCriteria,
|
||||
int bannerOpacity,
|
||||
CardSizeWidget *_cardSizeWidget)
|
||||
: QWidget(parent), deckListModel(_deckListModel), trackedIndex(_trackedIndex), zoneName(_zoneName),
|
||||
cardGroupCategory(_cardGroupCategory), activeGroupCriteria(_activeGroupCriteria),
|
||||
activeSortCriteria(_activeSortCriteria), cardSizeWidget(_cardSizeWidget)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
setMinimumSize(QSize(0, 0));
|
||||
|
||||
banner = new BannerWidget(this, cardGroupCategory, Qt::Orientation::Vertical, bannerOpacity);
|
||||
|
||||
layout->addWidget(banner);
|
||||
|
||||
CardGroupDisplayWidget::updateCardDisplays();
|
||||
|
||||
connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition);
|
||||
connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval);
|
||||
}
|
||||
|
||||
void CardGroupDisplayWidget::clearAllDisplayWidgets()
|
||||
{
|
||||
for (auto idx : indexToWidgetMap.keys()) {
|
||||
auto displayWidget = indexToWidgetMap.value(idx);
|
||||
removeFromLayout(displayWidget);
|
||||
indexToWidgetMap.remove(idx);
|
||||
delete displayWidget;
|
||||
}
|
||||
}
|
||||
|
||||
QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex index)
|
||||
{
|
||||
if (indexToWidgetMap.contains(index)) {
|
||||
return indexToWidgetMap[index];
|
||||
}
|
||||
auto cardName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString();
|
||||
auto cardProviderId = deckListModel->data(index.sibling(index.row(), 4), Qt::EditRole).toString();
|
||||
|
||||
auto widget = new CardInfoPictureWithTextOverlayWidget(getLayoutParent(), true);
|
||||
widget->setScaleFactor(cardSizeWidget->getSlider()->value());
|
||||
widget->setCard(CardDatabaseManager::getInstance()->getCard({cardName, cardProviderId}));
|
||||
|
||||
connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, &CardGroupDisplayWidget::onClick);
|
||||
connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &CardGroupDisplayWidget::onHover);
|
||||
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget, &CardInfoPictureWidget::setScaleFactor);
|
||||
|
||||
indexToWidgetMap.insert(index, widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
void CardGroupDisplayWidget::updateCardDisplays()
|
||||
{
|
||||
DeckListSortFilterProxyModel proxy;
|
||||
proxy.setSourceModel(deckListModel);
|
||||
proxy.setSortCriteria(activeSortCriteria);
|
||||
|
||||
// This doesn't really matter since overwrite the whole lessThan function to just compare dynamically anyway.
|
||||
proxy.setSortRole(Qt::EditRole);
|
||||
proxy.sort(1, Qt::AscendingOrder);
|
||||
|
||||
// 1. trackedIndex is a source index → map it to proxy space
|
||||
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);
|
||||
|
||||
// 2. iterate children under the proxy parent
|
||||
for (int i = 0; i < proxy.rowCount(proxyParent); ++i) {
|
||||
QModelIndex proxyIndex = proxy.index(i, 0, proxyParent);
|
||||
|
||||
// 3. map back to source
|
||||
QModelIndex sourceIndex = proxy.mapToSource(proxyIndex);
|
||||
|
||||
// 4. persist the source index
|
||||
QPersistentModelIndex persistent(sourceIndex);
|
||||
|
||||
addToLayout(constructWidgetForIndex(persistent));
|
||||
}
|
||||
}
|
||||
|
||||
void CardGroupDisplayWidget::onCardAddition(const QModelIndex &parent, int first, int last)
|
||||
{
|
||||
if (!trackedIndex.isValid()) {
|
||||
emit cleanupRequested(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent == trackedIndex) {
|
||||
for (int row = first; row <= last; ++row) {
|
||||
QModelIndex child = deckListModel->index(row, 0, parent);
|
||||
|
||||
// Persist the index
|
||||
QPersistentModelIndex persistent(child);
|
||||
|
||||
insertIntoLayout(constructWidgetForIndex(persistent), row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardGroupDisplayWidget::onCardRemoval(const QModelIndex &parent, int first, int last)
|
||||
{
|
||||
Q_UNUSED(first);
|
||||
Q_UNUSED(last);
|
||||
if (parent == trackedIndex) {
|
||||
for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) {
|
||||
if (!idx.isValid()) {
|
||||
removeFromLayout(indexToWidgetMap.value(idx));
|
||||
indexToWidgetMap.value(idx)->deleteLater();
|
||||
indexToWidgetMap.remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (!trackedIndex.isValid()) {
|
||||
emit cleanupRequested(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSortCriteria)
|
||||
{
|
||||
activeSortCriteria = std::move(_activeSortCriteria);
|
||||
|
||||
clearAllDisplayWidgets();
|
||||
updateCardDisplays();
|
||||
}
|
||||
|
||||
void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
|
||||
{
|
||||
emit cardClicked(event, card);
|
||||
}
|
||||
|
||||
void CardGroupDisplayWidget::onHover(const ExactCard &card)
|
||||
{
|
||||
emit cardHovered(card);
|
||||
}
|
||||
|
||||
void CardGroupDisplayWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
#ifndef CARD_GROUP_DISPLAY_WIDGET_H
|
||||
#define CARD_GROUP_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../../card/card_info.h"
|
||||
#include "../../../../deck/deck_list_model.h"
|
||||
#include "../../general/display/banner_widget.h"
|
||||
#include "../card_info_picture_with_text_overlay_widget.h"
|
||||
#include "../card_size_widget.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class CardGroupDisplayWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CardGroupDisplayWidget(QWidget *parent,
|
||||
DeckListModel *deckListModel,
|
||||
QPersistentModelIndex trackedIndex,
|
||||
QString zoneName,
|
||||
QString cardGroupCategory,
|
||||
QString activeGroupCriteria,
|
||||
QStringList activeSortCriteria,
|
||||
int bannerOpacity,
|
||||
CardSizeWidget *cardSizeWidget);
|
||||
void clearAllDisplayWidgets();
|
||||
|
||||
DeckListModel *deckListModel;
|
||||
QPersistentModelIndex trackedIndex;
|
||||
QHash<QPersistentModelIndex, QWidget *> indexToWidgetMap;
|
||||
QString zoneName;
|
||||
QString cardGroupCategory;
|
||||
QString activeGroupCriteria;
|
||||
QStringList activeSortCriteria;
|
||||
CardSizeWidget *cardSizeWidget;
|
||||
|
||||
public slots:
|
||||
void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card);
|
||||
void onHover(const ExactCard &card);
|
||||
virtual QWidget *constructWidgetForIndex(QPersistentModelIndex index);
|
||||
virtual void updateCardDisplays();
|
||||
virtual void onCardAddition(const QModelIndex &parent, int first, int last);
|
||||
virtual void onCardRemoval(const QModelIndex &parent, int first, int last);
|
||||
void onActiveSortCriteriaChanged(QStringList activeSortCriteria);
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
signals:
|
||||
void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card);
|
||||
void cardHovered(const ExactCard &card);
|
||||
void cleanupRequested(CardGroupDisplayWidget *cardGroupDisplayWidget);
|
||||
|
||||
protected:
|
||||
QVBoxLayout *layout;
|
||||
BannerWidget *banner;
|
||||
|
||||
virtual QWidget *getLayoutParent()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
virtual void addToLayout(QWidget *toAdd)
|
||||
{
|
||||
layout->addWidget(toAdd);
|
||||
}
|
||||
|
||||
virtual void insertIntoLayout(QWidget *toInsert, int insertAt)
|
||||
{
|
||||
layout->insertWidget(insertAt, toInsert);
|
||||
}
|
||||
|
||||
virtual void removeFromLayout(QWidget *toRemove)
|
||||
{
|
||||
layout->removeWidget(toRemove);
|
||||
}
|
||||
};
|
||||
#endif // CARD_GROUP_DISPLAY_WIDGET_H
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#include "flat_card_group_display_widget.h"
|
||||
|
||||
#include "../../../../database/card_database_manager.h"
|
||||
#include "../../../../deck/deck_list_model.h"
|
||||
#include "../../../../utility/card_info_comparator.h"
|
||||
#include "../card_info_picture_with_text_overlay_widget.h"
|
||||
|
||||
#include <QResizeEvent>
|
||||
#include <utility>
|
||||
|
||||
FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent,
|
||||
DeckListModel *_deckListModel,
|
||||
QPersistentModelIndex _trackedIndex,
|
||||
QString _zoneName,
|
||||
QString _cardGroupCategory,
|
||||
QString _activeGroupCriteria,
|
||||
QStringList _activeSortCriteria,
|
||||
int bannerOpacity,
|
||||
CardSizeWidget *_cardSizeWidget)
|
||||
: CardGroupDisplayWidget(parent,
|
||||
_deckListModel,
|
||||
std::move(_trackedIndex),
|
||||
_zoneName,
|
||||
_cardGroupCategory,
|
||||
_activeGroupCriteria,
|
||||
_activeSortCriteria,
|
||||
bannerOpacity,
|
||||
_cardSizeWidget)
|
||||
{
|
||||
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
|
||||
banner->setBuddy(flowWidget);
|
||||
|
||||
layout->addWidget(flowWidget);
|
||||
|
||||
for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) {
|
||||
FlatCardGroupDisplayWidget::removeFromLayout(indexToWidgetMap.value(idx));
|
||||
indexToWidgetMap.value(idx)->deleteLater();
|
||||
indexToWidgetMap.remove(idx);
|
||||
}
|
||||
|
||||
FlatCardGroupDisplayWidget::updateCardDisplays();
|
||||
disconnect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition);
|
||||
disconnect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval);
|
||||
|
||||
connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &FlatCardGroupDisplayWidget::onCardAddition);
|
||||
connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &FlatCardGroupDisplayWidget::onCardRemoval);
|
||||
}
|
||||
|
||||
void FlatCardGroupDisplayWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef FLAT_CARD_GROUP_DISPLAY_WIDGET_H
|
||||
#define FLAT_CARD_GROUP_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../general/layout_containers/flow_widget.h"
|
||||
#include "card_group_display_widget.h"
|
||||
|
||||
class FlatCardGroupDisplayWidget : public CardGroupDisplayWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FlatCardGroupDisplayWidget(QWidget *parent,
|
||||
DeckListModel *deckListModel,
|
||||
QPersistentModelIndex trackedIndex,
|
||||
QString zoneName,
|
||||
QString cardGroupCategory,
|
||||
QString activeGroupCriteria,
|
||||
QStringList activeSortCriteria,
|
||||
int bannerOpacity,
|
||||
CardSizeWidget *cardSizeWidget);
|
||||
|
||||
public slots:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
FlowWidget *flowWidget;
|
||||
|
||||
QWidget *getLayoutParent() override
|
||||
{
|
||||
return flowWidget;
|
||||
}
|
||||
|
||||
void addToLayout(QWidget *toAdd) override
|
||||
{
|
||||
flowWidget->addWidget(toAdd);
|
||||
}
|
||||
|
||||
void insertIntoLayout(QWidget *toInsert, int insertAt) override
|
||||
{
|
||||
flowWidget->insertWidgetAtIndex(toInsert, insertAt);
|
||||
}
|
||||
|
||||
void removeFromLayout(QWidget *toRemove) override
|
||||
{
|
||||
flowWidget->removeWidget(toRemove);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // FLAT_CARD_GROUP_DISPLAY_WIDGET_H
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#include "overlapped_card_group_display_widget.h"
|
||||
|
||||
#include "../../../../database/card_database_manager.h"
|
||||
#include "../../../../deck/deck_list_model.h"
|
||||
#include "../../../../utility/card_info_comparator.h"
|
||||
#include "../card_info_picture_with_text_overlay_widget.h"
|
||||
|
||||
#include <QResizeEvent>
|
||||
|
||||
OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *parent,
|
||||
DeckListModel *_deckListModel,
|
||||
QPersistentModelIndex _trackedIndex,
|
||||
QString _zoneName,
|
||||
QString _cardGroupCategory,
|
||||
QString _activeGroupCriteria,
|
||||
QStringList _activeSortCriteria,
|
||||
int bannerOpacity,
|
||||
CardSizeWidget *_cardSizeWidget)
|
||||
: CardGroupDisplayWidget(parent,
|
||||
_deckListModel,
|
||||
_trackedIndex,
|
||||
_zoneName,
|
||||
_cardGroupCategory,
|
||||
_activeGroupCriteria,
|
||||
_activeSortCriteria,
|
||||
bannerOpacity,
|
||||
_cardSizeWidget)
|
||||
{
|
||||
overlapWidget = new OverlapWidget(this, 80, 1, 1, Qt::Vertical, true);
|
||||
banner->setBuddy(overlapWidget);
|
||||
|
||||
layout->addWidget(overlapWidget);
|
||||
|
||||
for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) {
|
||||
OverlappedCardGroupDisplayWidget::removeFromLayout(indexToWidgetMap.value(idx));
|
||||
indexToWidgetMap.value(idx)->deleteLater();
|
||||
indexToWidgetMap.remove(idx);
|
||||
}
|
||||
|
||||
OverlappedCardGroupDisplayWidget::updateCardDisplays();
|
||||
|
||||
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, this,
|
||||
[this]() { overlapWidget->adjustMaxColumnsAndRows(); });
|
||||
|
||||
disconnect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition);
|
||||
disconnect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval);
|
||||
|
||||
connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &OverlappedCardGroupDisplayWidget::onCardAddition);
|
||||
connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &OverlappedCardGroupDisplayWidget::onCardRemoval);
|
||||
}
|
||||
|
||||
void OverlappedCardGroupDisplayWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
overlapWidget->resize(event->size());
|
||||
overlapWidget->adjustMaxColumnsAndRows();
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H
|
||||
#define OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../general/layout_containers/overlap_widget.h"
|
||||
#include "card_group_display_widget.h"
|
||||
|
||||
class OverlappedCardGroupDisplayWidget : public CardGroupDisplayWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OverlappedCardGroupDisplayWidget(QWidget *parent,
|
||||
DeckListModel *deckListModel,
|
||||
QPersistentModelIndex trackedIndex,
|
||||
QString zoneName,
|
||||
QString cardGroupCategory,
|
||||
QString activeGroupCriteria,
|
||||
QStringList activeSortCriteria,
|
||||
int bannerOpacity,
|
||||
CardSizeWidget *cardSizeWidget);
|
||||
|
||||
public slots:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
OverlapWidget *overlapWidget;
|
||||
|
||||
QWidget *getLayoutParent() override
|
||||
{
|
||||
return overlapWidget;
|
||||
}
|
||||
|
||||
void addToLayout(QWidget *toAdd) override
|
||||
{
|
||||
overlapWidget->addWidget(toAdd);
|
||||
}
|
||||
|
||||
void insertIntoLayout(QWidget *toInsert, int insertAt) override
|
||||
{
|
||||
overlapWidget->insertWidgetAtIndex(toInsert, insertAt);
|
||||
}
|
||||
|
||||
void removeFromLayout(QWidget *toRemove) override
|
||||
{
|
||||
overlapWidget->removeWidget(toRemove);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
#include "card_info_display_widget.h"
|
||||
|
||||
#include "../../../database/card_database_manager.h"
|
||||
#include "../../../game/board/card_item.h"
|
||||
#include "../../../main.h"
|
||||
#include "card_info_picture_widget.h"
|
||||
#include "card_info_text_widget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QScreen>
|
||||
#include <QVBoxLayout>
|
||||
#include <utility>
|
||||
|
||||
CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *parent, Qt::WindowFlags flags)
|
||||
: QFrame(parent, flags), aspectRatio((qreal)CARD_HEIGHT / (qreal)CARD_WIDTH)
|
||||
{
|
||||
setContentsMargins(3, 3, 3, 3);
|
||||
pic = new CardInfoPictureWidget();
|
||||
pic->setObjectName("pic");
|
||||
text = new CardInfoTextWidget();
|
||||
text->setObjectName("text");
|
||||
connect(text, &CardInfoTextWidget::linkActivated, this, [this](const QString &card) { setCard({card}); });
|
||||
|
||||
auto *layout = new QVBoxLayout();
|
||||
layout->setObjectName("layout");
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(pic, 0, Qt::AlignCenter);
|
||||
layout->addWidget(text, 0, Qt::AlignCenter);
|
||||
setLayout(layout);
|
||||
|
||||
setFrameStyle(QFrame::Panel | QFrame::Raised);
|
||||
|
||||
int pixmapHeight = QGuiApplication::primaryScreen()->geometry().height() / 3;
|
||||
int pixmapWidth = static_cast<int>(pixmapHeight / aspectRatio);
|
||||
pic->setFixedWidth(pixmapWidth);
|
||||
pic->setFixedHeight(pixmapHeight);
|
||||
setFixedWidth(pixmapWidth + 150);
|
||||
|
||||
setCard(cardRef);
|
||||
|
||||
// ensure our parent gets a valid size to position us correctly
|
||||
resize(width(), sizeHint().height());
|
||||
}
|
||||
|
||||
void CardInfoDisplayWidget::setCard(const ExactCard &card)
|
||||
{
|
||||
if (exactCard)
|
||||
disconnect(exactCard.getCardPtr().data(), nullptr, this, nullptr);
|
||||
exactCard = card;
|
||||
if (exactCard)
|
||||
connect(exactCard.getCardPtr().data(), &QObject::destroyed, this, &CardInfoDisplayWidget::clear);
|
||||
|
||||
text->setCard(exactCard.getCardPtr());
|
||||
pic->setCard(exactCard);
|
||||
}
|
||||
|
||||
void CardInfoDisplayWidget::setCard(const CardRef &cardRef)
|
||||
{
|
||||
setCard(CardDatabaseManager::getInstance()->guessCard(cardRef));
|
||||
if (exactCard.isEmpty()) {
|
||||
text->setInvalidCardName(cardRef.name);
|
||||
}
|
||||
}
|
||||
|
||||
void CardInfoDisplayWidget::setCard(AbstractCardItem *card)
|
||||
{
|
||||
setCard(card->getCard());
|
||||
}
|
||||
|
||||
void CardInfoDisplayWidget::clear()
|
||||
{
|
||||
setCard(ExactCard());
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef CARDINFOWIDGET_H
|
||||
#define CARDINFOWIDGET_H
|
||||
|
||||
#include "../../../card/exact_card.h"
|
||||
#include "card_ref.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QFrame>
|
||||
#include <QStringList>
|
||||
|
||||
class CardInfoPictureWidget;
|
||||
class CardInfoTextWidget;
|
||||
class AbstractCardItem;
|
||||
|
||||
class CardInfoDisplayWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
qreal aspectRatio;
|
||||
ExactCard exactCard;
|
||||
CardInfoPictureWidget *pic;
|
||||
CardInfoTextWidget *text;
|
||||
|
||||
public:
|
||||
explicit CardInfoDisplayWidget(const CardRef &cardRef, QWidget *parent = nullptr, Qt::WindowFlags f = {});
|
||||
|
||||
public slots:
|
||||
void setCard(const ExactCard &card);
|
||||
void setCard(const CardRef &cardRef);
|
||||
void setCard(AbstractCardItem *card);
|
||||
|
||||
private slots:
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
#include "card_info_frame_widget.h"
|
||||
|
||||
#include "../../../database/card_database_manager.h"
|
||||
#include "../../../game/board/card_item.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
#include "card_info_display_widget.h"
|
||||
#include "card_info_picture_widget.h"
|
||||
#include "card_info_text_widget.h"
|
||||
|
||||
#include <QSplitter>
|
||||
#include <QVBoxLayout>
|
||||
#include <utility>
|
||||
|
||||
CardInfoFrameWidget::CardInfoFrameWidget(QWidget *parent)
|
||||
: QTabWidget(parent), viewTransformationButton(nullptr), cardTextOnly(false)
|
||||
{
|
||||
setContentsMargins(3, 3, 3, 3);
|
||||
pic = new CardInfoPictureWidget();
|
||||
pic->setObjectName("pic");
|
||||
connect(pic, &CardInfoPictureWidget::cardChanged, this,
|
||||
qOverload<const ExactCard &>(&CardInfoFrameWidget::setCard));
|
||||
|
||||
text = new CardInfoTextWidget();
|
||||
text->setObjectName("text");
|
||||
connect(text, &CardInfoTextWidget::linkActivated, this, qOverload<const QString &>(&CardInfoFrameWidget::setCard));
|
||||
|
||||
tab1 = new QWidget(this);
|
||||
tab2 = new QWidget(this);
|
||||
tab3 = new QWidget(this);
|
||||
|
||||
tab1->setObjectName("tab1");
|
||||
tab2->setObjectName("tab2");
|
||||
tab3->setObjectName("tab3");
|
||||
|
||||
insertTab(ImageOnlyView, tab1, QString());
|
||||
insertTab(TextOnlyView, tab2, QString());
|
||||
insertTab(ImageAndTextView, tab3, QString());
|
||||
connect(this, &CardInfoFrameWidget::currentChanged, this, &CardInfoFrameWidget::setViewMode);
|
||||
|
||||
tab1Layout = new QVBoxLayout();
|
||||
tab1Layout->setObjectName("tab1Layout");
|
||||
tab1Layout->setContentsMargins(0, 0, 0, 0);
|
||||
tab1Layout->setSpacing(0);
|
||||
tab1->setLayout(tab1Layout);
|
||||
|
||||
tab2Layout = new QVBoxLayout();
|
||||
tab2Layout->setObjectName("tab2Layout");
|
||||
tab2Layout->setContentsMargins(0, 0, 0, 0);
|
||||
tab2Layout->setSpacing(0);
|
||||
tab2->setLayout(tab2Layout);
|
||||
|
||||
splitter = new QSplitter();
|
||||
splitter->setObjectName("splitter");
|
||||
splitter->setOrientation(Qt::Vertical);
|
||||
|
||||
tab3Layout = new QVBoxLayout();
|
||||
tab3Layout->setObjectName("tab3Layout");
|
||||
tab3Layout->setContentsMargins(0, 0, 0, 0);
|
||||
tab3Layout->setSpacing(0);
|
||||
tab3Layout->addWidget(splitter);
|
||||
tab3->setLayout(tab3Layout);
|
||||
|
||||
setViewMode(SettingsCache::instance().getCardInfoViewMode());
|
||||
}
|
||||
|
||||
void CardInfoFrameWidget::retranslateUi()
|
||||
{
|
||||
setTabText(ImageOnlyView, tr("Image"));
|
||||
setTabText(TextOnlyView, tr("Description"));
|
||||
setTabText(ImageAndTextView, tr("Both"));
|
||||
|
||||
if (viewTransformationButton) {
|
||||
viewTransformationButton->setText(tr("View transformation"));
|
||||
}
|
||||
}
|
||||
|
||||
void CardInfoFrameWidget::setViewTransformationButtonVisibility(bool visible)
|
||||
{
|
||||
if (!viewTransformationButton && visible) {
|
||||
viewTransformationButton = new QPushButton();
|
||||
viewTransformationButton->setObjectName("viewTransformationButton");
|
||||
connect(viewTransformationButton, &QPushButton::clicked, this, &CardInfoFrameWidget::viewTransformation);
|
||||
refreshLayout();
|
||||
} else if (viewTransformationButton && !visible) {
|
||||
// Deleting a widget automatically removes it from its parent
|
||||
viewTransformationButton->deleteLater();
|
||||
viewTransformationButton = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the widgets to the layouts that are relevant to the currently active tab.
|
||||
*
|
||||
* QWidgets can only have one parent, so we need to re-parent the shared widgets whenever we switch tabs.
|
||||
*/
|
||||
void CardInfoFrameWidget::refreshLayout()
|
||||
{
|
||||
switch (currentIndex()) {
|
||||
case ImageOnlyView:
|
||||
case TextOnlyView:
|
||||
// We need to always parent all widgets, even the ones that aren't visible,
|
||||
// since an unparented widget becomes free-floating.
|
||||
tab1Layout->addWidget(pic);
|
||||
if (viewTransformationButton) {
|
||||
tab1Layout->addWidget(viewTransformationButton);
|
||||
}
|
||||
tab2Layout->addWidget(text);
|
||||
break;
|
||||
case ImageAndTextView:
|
||||
splitter->addWidget(pic);
|
||||
if (viewTransformationButton) {
|
||||
splitter->addWidget(viewTransformationButton);
|
||||
}
|
||||
splitter->addWidget(text);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void CardInfoFrameWidget::setViewMode(int mode)
|
||||
{
|
||||
if (currentIndex() != mode) {
|
||||
setCurrentIndex(mode);
|
||||
}
|
||||
|
||||
refreshLayout();
|
||||
|
||||
SettingsCache::instance().setCardInfoViewMode(mode);
|
||||
}
|
||||
|
||||
static bool hasTransformation(const CardInfo &info)
|
||||
{
|
||||
for (const auto &cardRelation : info.getAllRelatedCards()) {
|
||||
if (cardRelation->getDoesTransform()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CardInfoFrameWidget::setCard(const ExactCard &card)
|
||||
{
|
||||
if (exactCard) {
|
||||
disconnect(exactCard.getCardPtr().data(), nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
exactCard = card;
|
||||
|
||||
if (exactCard) {
|
||||
connect(exactCard.getCardPtr().data(), &QObject::destroyed, this, &CardInfoFrameWidget::clearCard);
|
||||
}
|
||||
|
||||
setViewTransformationButtonVisibility(hasTransformation(exactCard.getInfo()));
|
||||
|
||||
text->setCard(exactCard.getCardPtr());
|
||||
pic->setCard(exactCard);
|
||||
}
|
||||
|
||||
void CardInfoFrameWidget::setCard(const QString &cardName)
|
||||
{
|
||||
setCard(CardDatabaseManager::getInstance()->guessCard({cardName}));
|
||||
}
|
||||
|
||||
void CardInfoFrameWidget::setCard(const CardRef &cardRef)
|
||||
{
|
||||
setCard(CardDatabaseManager::getInstance()->getCard(cardRef));
|
||||
}
|
||||
|
||||
void CardInfoFrameWidget::setCard(AbstractCardItem *card)
|
||||
{
|
||||
if (card) {
|
||||
setCard(card->getCard());
|
||||
}
|
||||
}
|
||||
|
||||
void CardInfoFrameWidget::viewTransformation()
|
||||
{
|
||||
if (exactCard) {
|
||||
const auto &cardRelations = exactCard.getInfo().getAllRelatedCards();
|
||||
for (const auto &cardRelation : cardRelations) {
|
||||
if (cardRelation->getDoesTransform()) {
|
||||
setCard(cardRelation->getName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardInfoFrameWidget::clearCard()
|
||||
{
|
||||
setCard(ExactCard());
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#ifndef CARDFRAME_H
|
||||
#define CARDFRAME_H
|
||||
|
||||
#include "../../../card/exact_card.h"
|
||||
#include "card_ref.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QTabWidget>
|
||||
|
||||
class AbstractCardItem;
|
||||
class CardInfoPictureWidget;
|
||||
class CardInfoTextWidget;
|
||||
class QVBoxLayout;
|
||||
class QSplitter;
|
||||
|
||||
class CardInfoFrameWidget : public QTabWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
ExactCard exactCard;
|
||||
CardInfoPictureWidget *pic;
|
||||
CardInfoTextWidget *text;
|
||||
QPushButton *viewTransformationButton;
|
||||
bool cardTextOnly;
|
||||
QWidget *tab1, *tab2, *tab3;
|
||||
QVBoxLayout *tab1Layout, *tab2Layout, *tab3Layout;
|
||||
QSplitter *splitter;
|
||||
|
||||
void setViewTransformationButtonVisibility(bool visible);
|
||||
void refreshLayout();
|
||||
|
||||
public:
|
||||
enum ViewMode
|
||||
{
|
||||
ImageOnlyView,
|
||||
TextOnlyView,
|
||||
ImageAndTextView
|
||||
};
|
||||
|
||||
explicit CardInfoFrameWidget(QWidget *parent = nullptr);
|
||||
ExactCard getCard()
|
||||
{
|
||||
return exactCard;
|
||||
}
|
||||
void retranslateUi();
|
||||
|
||||
public slots:
|
||||
void setCard(const ExactCard &card);
|
||||
void setCard(const QString &cardName);
|
||||
void setCard(const CardRef &cardRef);
|
||||
void setCard(AbstractCardItem *card);
|
||||
void viewTransformation();
|
||||
void clearCard();
|
||||
void setViewMode(int mode);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#include "card_info_picture_art_crop_widget.h"
|
||||
|
||||
#include "../../../picture_loader/picture_loader.h"
|
||||
|
||||
CardInfoPictureArtCropWidget::CardInfoPictureArtCropWidget(QWidget *parent)
|
||||
: CardInfoPictureWidget(parent, false, false)
|
||||
{
|
||||
hide();
|
||||
}
|
||||
|
||||
QPixmap CardInfoPictureArtCropWidget::getProcessedBackground(const QSize &targetSize)
|
||||
{
|
||||
// Load the full-resolution card image, not a pre-scaled one
|
||||
QPixmap fullResPixmap;
|
||||
if (getCard()) {
|
||||
PictureLoader::getPixmap(fullResPixmap, getCard(), QSize(745, 1040)); // or a high default size
|
||||
} else {
|
||||
PictureLoader::getCardBackPixmap(fullResPixmap, QSize(745, 1040));
|
||||
}
|
||||
|
||||
// Fail-safe if loading failed
|
||||
if (fullResPixmap.isNull()) {
|
||||
return QPixmap(targetSize);
|
||||
}
|
||||
|
||||
const QSize sz = fullResPixmap.size();
|
||||
|
||||
int marginX = sz.width() * 0.07;
|
||||
int topMargin = sz.height() * 0.11;
|
||||
int bottomMargin = sz.height() * 0.45;
|
||||
|
||||
QRect foilRect(marginX, topMargin, sz.width() - 2 * marginX, sz.height() - topMargin - bottomMargin);
|
||||
|
||||
foilRect = foilRect.intersected(fullResPixmap.rect()); // always clamp to source bounds
|
||||
|
||||
// Crop first, then scale for best quality
|
||||
QPixmap cropped = fullResPixmap.copy(foilRect);
|
||||
QPixmap scaled = cropped.scaled(targetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||
|
||||
return scaled;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef CARD_INFO_PICTURE_ART_CROP_WIDGET_H
|
||||
#define CARD_INFO_PICTURE_ART_CROP_WIDGET_H
|
||||
|
||||
#include "card_info_picture_widget.h"
|
||||
|
||||
class CardInfoPictureArtCropWidget : public CardInfoPictureWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CardInfoPictureArtCropWidget(QWidget *parent = nullptr);
|
||||
|
||||
// Returns a processed (cropped & scaled) version of the pixmap
|
||||
QPixmap getProcessedBackground(const QSize &targetSize);
|
||||
};
|
||||
|
||||
#endif // CARD_INFO_PICTURE_ART_CROP_WIDGET_H
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
#include "card_info_picture_enlarged_widget.h"
|
||||
|
||||
#include "../../../picture_loader/picture_loader.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
|
||||
#include <QPainterPath>
|
||||
#include <QStylePainter>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* @brief Constructs a CardPictureEnlargedWidget.
|
||||
* @param parent The parent widget.
|
||||
*
|
||||
* Sets the widget's window flags to keep it displayed as a tooltip overlay.
|
||||
*/
|
||||
CardInfoPictureEnlargedWidget::CardInfoPictureEnlargedWidget(QWidget *parent) : QWidget(parent), pixmapDirty(true)
|
||||
{
|
||||
setWindowFlags(Qt::ToolTip); // Keeps this widget on top of everything
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
|
||||
Q_UNUSED(_roundCardCorners);
|
||||
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads the pixmap based on the given size and card information.
|
||||
* @param size The desired size for the loaded pixmap.
|
||||
*
|
||||
* If card information is available, it loads the card's specific pixmap. Otherwise, it loads a default card back
|
||||
* pixmap.
|
||||
*/
|
||||
void CardInfoPictureEnlargedWidget::loadPixmap(const QSize &size)
|
||||
{
|
||||
if (card) {
|
||||
PictureLoader::getPixmap(enlargedPixmap, card, size);
|
||||
} else {
|
||||
PictureLoader::getCardBackPixmap(enlargedPixmap, size);
|
||||
}
|
||||
pixmapDirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the pixmap for the widget based on a provided card.
|
||||
* @param _card The card information to load.
|
||||
* @param size The desired size for the pixmap.
|
||||
*
|
||||
* Sets the widget's pixmap to the card image and resizes the widget to match the specified size. Triggers a repaint.
|
||||
*/
|
||||
void CardInfoPictureEnlargedWidget::setCardPixmap(const ExactCard &_card, const QSize size)
|
||||
{
|
||||
card = _card;
|
||||
loadPixmap(size);
|
||||
|
||||
setFixedSize(size); // Set the widget size to the enlarged size
|
||||
|
||||
update(); // Trigger a repaint
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Custom paint event that draws the enlarged card image with rounded corners.
|
||||
* @param event The paint event (unused).
|
||||
*
|
||||
* Checks if the pixmap is valid. Then, calculates the size and position for centering the
|
||||
* scaled pixmap within the widget, applies rounded corners, and draws the pixmap.
|
||||
*/
|
||||
void CardInfoPictureEnlargedWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
if (width() == 0 || height() == 0 || enlargedPixmap.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pixmapDirty) {
|
||||
loadPixmap(size());
|
||||
}
|
||||
|
||||
// Scale the size of the pixmap to fit the widget while maintaining the aspect ratio
|
||||
QSize scaledSize = enlargedPixmap.size().scaled(size().width(), size().height(), Qt::KeepAspectRatio);
|
||||
|
||||
// Calculate the position to center the scaled pixmap
|
||||
QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2};
|
||||
|
||||
// Define the radius for rounded corners
|
||||
// Adjust the radius as needed for rounded corners
|
||||
qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * scaledSize.width() : 0.;
|
||||
|
||||
QStylePainter painter(this);
|
||||
// Fill the background with transparent color to ensure rounded corners are rendered properly
|
||||
painter.fillRect(rect(), Qt::transparent); // Use the transparent background
|
||||
|
||||
QPainterPath shape;
|
||||
shape.addRoundedRect(QRect(topLeft, scaledSize), radius, radius);
|
||||
painter.setClipPath(shape); // Set the clipping path
|
||||
|
||||
// Draw the pixmap scaled to the calculated size
|
||||
painter.drawItemPixmap(QRect(topLeft, scaledSize), Qt::AlignCenter,
|
||||
enlargedPixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef CARD_PICTURE_ENLARGED_WIDGET_H
|
||||
#define CARD_PICTURE_ENLARGED_WIDGET_H
|
||||
|
||||
#include "../../../card/exact_card.h"
|
||||
|
||||
#include <QPixmap>
|
||||
#include <QWidget>
|
||||
|
||||
class CardInfoPictureEnlargedWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
explicit CardInfoPictureEnlargedWidget(QWidget *parent = nullptr);
|
||||
|
||||
// Sets the card pixmap to display
|
||||
void setCardPixmap(const ExactCard &_card, QSize size);
|
||||
|
||||
protected:
|
||||
// Handles the painting event for the enlarged card
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
// Cached pixmap for the enlarged card
|
||||
QPixmap enlargedPixmap;
|
||||
|
||||
// Tracks if the pixmap needs to be refreshed/redrawn
|
||||
bool pixmapDirty;
|
||||
|
||||
// Card information
|
||||
ExactCard card;
|
||||
|
||||
// Loads the enlarged card pixmap
|
||||
void loadPixmap(const QSize &size);
|
||||
};
|
||||
|
||||
#endif // CARD_PICTURE_ENLARGED_WIDGET_H
|
||||
|
|
@ -0,0 +1,473 @@
|
|||
#include "card_info_picture_widget.h"
|
||||
|
||||
#include "../../../database/card_database_manager.h"
|
||||
#include "../../../game/board/card_item.h"
|
||||
#include "../../../picture_loader/picture_loader.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
#include "../../../tabs/tab_supervisor.h"
|
||||
#include "../../window_main.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QMouseEvent>
|
||||
#include <QScreen>
|
||||
#include <QStylePainter>
|
||||
#include <QWidget>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* @class CardInfoPictureWidget
|
||||
* @brief Widget that displays an enlarged image of a card, loading the image based on the card's info or showing a
|
||||
* default image.
|
||||
*
|
||||
* This widget can optionally display a larger version of the card's image when hovered over,
|
||||
* depending on the `hoverToZoomEnabled` parameter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Constructs a CardInfoPictureWidget.
|
||||
* @param parent The parent widget, if any.
|
||||
* @param hoverToZoomEnabled If this widget will spawn a larger widget when hovered over.
|
||||
*
|
||||
* Initializes the widget with a minimum height and sets the pixmap to a dirty state for initial loading.
|
||||
*/
|
||||
CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverToZoomEnabled, const bool _raiseOnEnter)
|
||||
: QWidget(parent), pixmapDirty(true), hoverToZoomEnabled(_hoverToZoomEnabled), raiseOnEnter(_raiseOnEnter)
|
||||
{
|
||||
setMinimumHeight(baseHeight);
|
||||
if (hoverToZoomEnabled) {
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(this->window());
|
||||
enlargedPixmapWidget->hide();
|
||||
connect(this, &QObject::destroyed, enlargedPixmapWidget, &CardInfoPictureEnlargedWidget::deleteLater);
|
||||
|
||||
hoverTimer = new QTimer(this);
|
||||
hoverTimer->setSingleShot(true);
|
||||
connect(hoverTimer, &QTimer::timeout, this, &CardInfoPictureWidget::showEnlargedPixmap);
|
||||
|
||||
// Store the widget's original position
|
||||
originalPos = this->pos();
|
||||
|
||||
// Create the animation
|
||||
animation = new QPropertyAnimation(this, "pos");
|
||||
animation->setDuration(200); // 200ms animation duration
|
||||
animation->setEasingCurve(QEasingCurve::OutQuad);
|
||||
|
||||
animation->setStartValue(originalPos);
|
||||
animation->setEndValue(originalPos - QPoint(0, animationOffset));
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
|
||||
Q_UNUSED(_roundCardCorners);
|
||||
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the card to be displayed and updates the pixmap.
|
||||
* @param card A shared pointer to the card information (CardInfoPtr).
|
||||
*
|
||||
* Disconnects any existing signal connections from the previous card info and connects to the `pixmapUpdated`
|
||||
* signal of the new card to automatically update the pixmap when the card image changes.
|
||||
*/
|
||||
void CardInfoPictureWidget::setCard(const ExactCard &card)
|
||||
{
|
||||
if (exactCard.getCardPtr()) {
|
||||
disconnect(exactCard.getCardPtr().data(), nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
exactCard = card;
|
||||
|
||||
if (exactCard.getCardPtr()) {
|
||||
connect(exactCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, &CardInfoPictureWidget::updatePixmap);
|
||||
}
|
||||
|
||||
updatePixmap();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the hover to zoom feature.
|
||||
* @param enabled If true, enables the hover-to-zoom functionality; otherwise, disables it.
|
||||
*/
|
||||
void CardInfoPictureWidget::setHoverToZoomEnabled(const bool enabled)
|
||||
{
|
||||
hoverToZoomEnabled = enabled;
|
||||
setMouseTracking(enabled);
|
||||
}
|
||||
|
||||
void CardInfoPictureWidget::setRaiseOnEnterEnabled(const bool enabled)
|
||||
{
|
||||
raiseOnEnter = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles widget resizing by updating the pixmap size.
|
||||
* @param event The resize event (unused).
|
||||
*
|
||||
* Calls `updatePixmap()` to ensure the image scales appropriately when the widget is resized.
|
||||
*/
|
||||
void CardInfoPictureWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
originalPos = pos(); // Update the baseline position
|
||||
updatePixmap();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the scale factor for the widget.
|
||||
* @param scale The scale factor to apply.
|
||||
*
|
||||
* Adjusts the widget's size according to the scale factor and updates the pixmap.
|
||||
*/
|
||||
void CardInfoPictureWidget::setScaleFactor(const int scale)
|
||||
{
|
||||
const int newWidth = baseWidth * scale / 100;
|
||||
const int newHeight = static_cast<int>(newWidth * aspectRatio);
|
||||
|
||||
scaleFactor = scale;
|
||||
|
||||
setFixedSize(newWidth, newHeight);
|
||||
updatePixmap();
|
||||
|
||||
emit cardScaleFactorChanged(scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Marks the pixmap as dirty and triggers a widget repaint.
|
||||
*
|
||||
* Sets `pixmapDirty` to true, indicating that the pixmap needs to be reloaded before the next display.
|
||||
*/
|
||||
void CardInfoPictureWidget::updatePixmap()
|
||||
{
|
||||
pixmapDirty = true;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads the appropriate pixmap based on the current card info.
|
||||
*
|
||||
* If `info` is valid, loads the card's image. Otherwise, loads a default card back image.
|
||||
*/
|
||||
void CardInfoPictureWidget::loadPixmap()
|
||||
{
|
||||
PictureLoader::getCardBackLoadingInProgressPixmap(resizedPixmap, size());
|
||||
if (exactCard) {
|
||||
PictureLoader::getPixmap(resizedPixmap, exactCard, size());
|
||||
} else {
|
||||
PictureLoader::getCardBackLoadingFailedPixmap(resizedPixmap, size());
|
||||
}
|
||||
|
||||
pixmapDirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Custom paint event that draws the card image with rounded corners.
|
||||
* @param event The paint event (unused).
|
||||
*
|
||||
* Checks if the pixmap needs to be reloaded. Then, calculates the size and position for centering the
|
||||
* scaled pixmap within the widget, applies rounded corners, and draws the pixmap.
|
||||
*/
|
||||
void CardInfoPictureWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QWidget::paintEvent(event);
|
||||
|
||||
if (width() == 0 || height() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pixmapDirty) {
|
||||
loadPixmap();
|
||||
}
|
||||
|
||||
QPixmap transformedPixmap = resizedPixmap; // Default pixmap
|
||||
if (SettingsCache::instance().getAutoRotateSidewaysLayoutCards()) {
|
||||
if (exactCard.getInfo().getLandscapeOrientation()) {
|
||||
// Rotate pixmap 90 degrees to the left
|
||||
QTransform transform;
|
||||
transform.rotate(90);
|
||||
transformedPixmap = resizedPixmap.transformed(transform, Qt::SmoothTransformation);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle DPI scaling
|
||||
qreal dpr = devicePixelRatio(); // Get the actual scaling factor
|
||||
QSize availableSize = size() * dpr; // Convert to physical pixel size
|
||||
|
||||
// Compute final scaled size
|
||||
QSize pixmapSize = transformedPixmap.size();
|
||||
QSize scaledSize = pixmapSize.scaled(availableSize, Qt::KeepAspectRatio);
|
||||
|
||||
// Pre-scale the pixmap once before drawing
|
||||
QPixmap finalPixmap = transformedPixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
finalPixmap.setDevicePixelRatio(dpr); // Ensure correct display on high-DPI screens
|
||||
|
||||
// Compute target rectangle with explicit integer conversion
|
||||
int targetX = static_cast<int>((availableSize.width() - scaledSize.width()) / (2 * dpr));
|
||||
int targetY = static_cast<int>((availableSize.height() - scaledSize.height()) / (2 * dpr));
|
||||
int targetW = static_cast<int>(scaledSize.width() / dpr);
|
||||
int targetH = static_cast<int>(scaledSize.height() / dpr);
|
||||
QRect targetRect{targetX, targetY, targetW, targetH};
|
||||
|
||||
// Compute rounded corner radius
|
||||
// Ensure consistent rounding
|
||||
qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * static_cast<qreal>(targetRect.width()) : 0.;
|
||||
|
||||
// Draw the pixmap with rounded corners
|
||||
QStylePainter painter(this);
|
||||
QPainterPath shape;
|
||||
shape.addRoundedRect(targetRect, radius, radius);
|
||||
painter.setClipPath(shape);
|
||||
|
||||
// Draw the pre-scaled pixmap directly
|
||||
painter.drawPixmap(targetRect, finalPixmap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides the recommended size for the widget based on the scale factor.
|
||||
* @return The recommended widget size.
|
||||
*/
|
||||
QSize CardInfoPictureWidget::sizeHint() const
|
||||
{
|
||||
return {static_cast<int>(baseWidth * scaleFactor / 100.0),
|
||||
static_cast<int>(baseWidth * scaleFactor / 100.0 * aspectRatio)};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Starts the hover timer to show the enlarged pixmap on hover.
|
||||
* @param event The enter event.
|
||||
*/
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void CardInfoPictureWidget::enterEvent(QEnterEvent *event)
|
||||
#else
|
||||
void CardInfoPictureWidget::enterEvent(QEvent *event)
|
||||
#endif
|
||||
{
|
||||
QWidget::enterEvent(event); // Call the base class implementation
|
||||
|
||||
// If hover-to-zoom is enabled, start the hover timer
|
||||
if (hoverToZoomEnabled) {
|
||||
hoverTimer->start(hoverActivateThresholdInMs);
|
||||
}
|
||||
|
||||
// Emit signal indicating a card is being hovered on
|
||||
emit hoveredOnCard(exactCard);
|
||||
|
||||
if (raiseOnEnter) {
|
||||
if (animation->state() == QAbstractAnimation::Running) {
|
||||
animation->pause(); // Pause current animation
|
||||
} else {
|
||||
originalPos = this->pos(); // Update the baseline position
|
||||
animation->setStartValue(originalPos);
|
||||
animation->setEndValue(originalPos - QPoint(0, animationOffset));
|
||||
}
|
||||
animation->setDirection(QAbstractAnimation::Forward);
|
||||
animation->start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stops the hover timer and hides the enlarged pixmap when the mouse leaves.
|
||||
* @param event The leave event.
|
||||
*/
|
||||
void CardInfoPictureWidget::leaveEvent(QEvent *event)
|
||||
{
|
||||
QWidget::leaveEvent(event);
|
||||
|
||||
if (hoverToZoomEnabled) {
|
||||
hoverTimer->stop();
|
||||
enlargedPixmapWidget->hide();
|
||||
}
|
||||
|
||||
if (raiseOnEnter) {
|
||||
if (animation->state() == QAbstractAnimation::Running) {
|
||||
animation->pause(); // Pause current animation
|
||||
}
|
||||
animation->setDirection(QAbstractAnimation::Backward);
|
||||
animation->start();
|
||||
}
|
||||
}
|
||||
|
||||
void CardInfoPictureWidget::moveEvent(QMoveEvent *event)
|
||||
{
|
||||
QWidget::moveEvent(event);
|
||||
|
||||
hoverTimer->stop();
|
||||
enlargedPixmapWidget->hide();
|
||||
|
||||
if (animation->state() == QAbstractAnimation::Running) {
|
||||
return;
|
||||
}
|
||||
originalPos = this->pos(); // Update the baseline position
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Moves the enlarged pixmap widget to follow the mouse cursor.
|
||||
* @param event The mouse move event.
|
||||
*/
|
||||
void CardInfoPictureWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mouseMoveEvent(event);
|
||||
|
||||
if (hoverToZoomEnabled && enlargedPixmapWidget->isVisible()) {
|
||||
const QPoint cursorPos = QCursor::pos();
|
||||
const QRect screenGeometry = QGuiApplication::screenAt(cursorPos)->geometry();
|
||||
const QSize widgetSize = enlargedPixmapWidget->size();
|
||||
|
||||
int newX = cursorPos.x() + enlargedPixmapOffset;
|
||||
int newY = cursorPos.y() + enlargedPixmapOffset;
|
||||
|
||||
// Adjust if out of bounds
|
||||
if (newX + widgetSize.width() > screenGeometry.right()) {
|
||||
newX = cursorPos.x() - widgetSize.width() - enlargedPixmapOffset;
|
||||
}
|
||||
if (newY + widgetSize.height() > screenGeometry.bottom()) {
|
||||
newY = cursorPos.y() - widgetSize.height() - enlargedPixmapOffset;
|
||||
}
|
||||
|
||||
enlargedPixmapWidget->move(newX, newY);
|
||||
}
|
||||
}
|
||||
|
||||
void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mousePressEvent(event);
|
||||
if (event->button() == Qt::RightButton) {
|
||||
createRightClickMenu()->popup(QCursor::pos());
|
||||
} else {
|
||||
emit cardClicked();
|
||||
}
|
||||
|
||||
emit cardClicked();
|
||||
}
|
||||
|
||||
void CardInfoPictureWidget::hideEvent(QHideEvent *event)
|
||||
{
|
||||
enlargedPixmapWidget->hide();
|
||||
QWidget::hideEvent(event);
|
||||
}
|
||||
|
||||
QMenu *CardInfoPictureWidget::createRightClickMenu()
|
||||
{
|
||||
auto *cardMenu = new QMenu(this);
|
||||
|
||||
if (!exactCard) {
|
||||
return cardMenu;
|
||||
}
|
||||
|
||||
cardMenu->addMenu(createViewRelatedCardsMenu());
|
||||
cardMenu->addMenu(createAddToOpenDeckMenu());
|
||||
|
||||
return cardMenu;
|
||||
}
|
||||
|
||||
QMenu *CardInfoPictureWidget::createViewRelatedCardsMenu()
|
||||
{
|
||||
auto viewRelatedCards = new QMenu(tr("View related cards"));
|
||||
|
||||
QList<CardRelation *> relatedCards = exactCard.getInfo().getAllRelatedCards();
|
||||
|
||||
auto relatedCardExists = [](const CardRelation *cardRelation) {
|
||||
return CardDatabaseManager::getInstance()->getCardInfo(cardRelation->getName()) != nullptr;
|
||||
};
|
||||
|
||||
bool atLeastOneGoodRelationFound = std::any_of(relatedCards.begin(), relatedCards.end(), relatedCardExists);
|
||||
|
||||
if (!atLeastOneGoodRelationFound) {
|
||||
viewRelatedCards->setEnabled(false);
|
||||
return viewRelatedCards;
|
||||
}
|
||||
|
||||
for (const auto &relatedCard : relatedCards) {
|
||||
const auto &relatedCardName = relatedCard->getName();
|
||||
QAction *viewCard = viewRelatedCards->addAction(relatedCardName);
|
||||
connect(viewCard, &QAction::triggered, this, [this, &relatedCardName] {
|
||||
emit cardChanged(
|
||||
CardDatabaseManager::getInstance()->getCard({relatedCardName, exactCard.getPrinting().getUuid()}));
|
||||
});
|
||||
viewRelatedCards->addAction(viewCard);
|
||||
}
|
||||
|
||||
return viewRelatedCards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the single instance of the MainWindow in this application.
|
||||
*/
|
||||
static MainWindow *findMainWindow()
|
||||
{
|
||||
for (auto widget : QApplication::topLevelWidgets()) {
|
||||
if (auto mainWindow = qobject_cast<MainWindow *>(widget)) {
|
||||
return mainWindow;
|
||||
}
|
||||
}
|
||||
// This code should be unreachable
|
||||
qCritical() << "Could not find MainWindow in QApplication::topLevelWidgets";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QMenu *CardInfoPictureWidget::createAddToOpenDeckMenu()
|
||||
{
|
||||
auto addToOpenDeckMenu = new QMenu(tr("Add card to deck"));
|
||||
|
||||
auto mainWindow = findMainWindow();
|
||||
QList<AbstractTabDeckEditor *> deckEditorTabs = mainWindow->getTabSupervisor()->getDeckEditorTabs();
|
||||
|
||||
if (deckEditorTabs.isEmpty()) {
|
||||
addToOpenDeckMenu->setEnabled(false);
|
||||
return addToOpenDeckMenu;
|
||||
}
|
||||
|
||||
for (auto &deckEditorTab : deckEditorTabs) {
|
||||
auto *addCardMenu = addToOpenDeckMenu->addMenu(deckEditorTab->getTabText());
|
||||
|
||||
QAction *addCard = addCardMenu->addAction(tr("Mainboard"));
|
||||
connect(addCard, &QAction::triggered, this, [this, deckEditorTab] {
|
||||
deckEditorTab->updateCard(exactCard);
|
||||
deckEditorTab->actAddCard(exactCard);
|
||||
});
|
||||
|
||||
QAction *addCardSideboard = addCardMenu->addAction(tr("Sideboard"));
|
||||
connect(addCardSideboard, &QAction::triggered, this, [this, deckEditorTab] {
|
||||
deckEditorTab->updateCard(exactCard);
|
||||
deckEditorTab->actAddCardToSideboard(exactCard);
|
||||
});
|
||||
}
|
||||
|
||||
return addToOpenDeckMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Displays the enlarged version of the card's pixmap near the cursor.
|
||||
*
|
||||
* If card information is available, the enlarged pixmap is loaded, positioned near the cursor,
|
||||
* and displayed.
|
||||
*/
|
||||
void CardInfoPictureWidget::showEnlargedPixmap() const
|
||||
{
|
||||
if (!exactCard) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QSize enlargedSize(static_cast<int>(size().width() * 2), static_cast<int>(size().width() * aspectRatio * 2));
|
||||
enlargedPixmapWidget->setCardPixmap(exactCard, enlargedSize);
|
||||
|
||||
const QPoint cursorPos = QCursor::pos();
|
||||
const QRect screenGeometry = QGuiApplication::screenAt(cursorPos)->geometry();
|
||||
const QSize widgetSize = enlargedPixmapWidget->size();
|
||||
|
||||
int newX = cursorPos.x() + enlargedPixmapOffset;
|
||||
int newY = cursorPos.y() + enlargedPixmapOffset;
|
||||
|
||||
// Adjust if out of bounds
|
||||
if (newX + widgetSize.width() > screenGeometry.right()) {
|
||||
newX = cursorPos.x() - widgetSize.width() - enlargedPixmapOffset;
|
||||
}
|
||||
if (newY + widgetSize.height() > screenGeometry.bottom()) {
|
||||
newY = cursorPos.y() - widgetSize.height() - enlargedPixmapOffset;
|
||||
}
|
||||
|
||||
enlargedPixmapWidget->move(newX, newY);
|
||||
|
||||
enlargedPixmapWidget->show();
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
#ifndef CARD_INFO_PICTURE_H
|
||||
#define CARD_INFO_PICTURE_H
|
||||
|
||||
#include "../../../card/exact_card.h"
|
||||
#include "card_info_picture_enlarged_widget.h"
|
||||
|
||||
#include <QPropertyAnimation>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CardInfoPictureWidgetLog, "card_info_picture_widget");
|
||||
|
||||
class AbstractCardItem;
|
||||
class QMenu;
|
||||
|
||||
class CardInfoPictureWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CardInfoPictureWidget(QWidget *parent = nullptr,
|
||||
bool hoverToZoomEnabled = false,
|
||||
bool raiseOnEnter = false);
|
||||
ExactCard getCard()
|
||||
{
|
||||
return exactCard;
|
||||
}
|
||||
[[nodiscard]] QSize sizeHint() const override;
|
||||
|
||||
public slots:
|
||||
void setCard(const ExactCard &card);
|
||||
void setScaleFactor(int scale); // New slot for scaling
|
||||
void setHoverToZoomEnabled(bool enabled);
|
||||
void setRaiseOnEnterEnabled(bool enabled);
|
||||
void updatePixmap();
|
||||
|
||||
signals:
|
||||
void hoveredOnCard(const ExactCard &hoveredCard);
|
||||
void cardScaleFactorChanged(int _scale);
|
||||
void cardChanged(const ExactCard &card);
|
||||
void cardClicked();
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void enterEvent(QEnterEvent *event) override; // Qt6 signature
|
||||
#else
|
||||
void enterEvent(QEvent *event) override; // Qt5 signature
|
||||
#endif
|
||||
void leaveEvent(QEvent *event) override;
|
||||
void moveEvent(QMoveEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
void loadPixmap();
|
||||
[[nodiscard]] const QPixmap &getResizedPixmap() const
|
||||
{
|
||||
return resizedPixmap;
|
||||
}
|
||||
void showEnlargedPixmap() const;
|
||||
|
||||
private:
|
||||
ExactCard exactCard;
|
||||
qreal magicTheGatheringCardAspectRatio = 1.396;
|
||||
qreal yuGiOhCardAspectRatio = 1.457;
|
||||
qreal aspectRatio = magicTheGatheringCardAspectRatio;
|
||||
int baseWidth = 200;
|
||||
int baseHeight = 200;
|
||||
double scaleFactor = 100;
|
||||
QPixmap resizedPixmap;
|
||||
bool pixmapDirty;
|
||||
bool hoverToZoomEnabled;
|
||||
bool raiseOnEnter;
|
||||
int hoverActivateThresholdInMs = 500;
|
||||
CardInfoPictureEnlargedWidget *enlargedPixmapWidget = nullptr;
|
||||
int enlargedPixmapOffset = 10;
|
||||
QTimer *hoverTimer;
|
||||
QPropertyAnimation *animation;
|
||||
QPoint originalPos; // Store the original position
|
||||
const int animationOffset = 10; // Adjust this for how much the widget moves up
|
||||
|
||||
QMenu *createRightClickMenu();
|
||||
QMenu *createViewRelatedCardsMenu();
|
||||
QMenu *createAddToOpenDeckMenu();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
#include "card_info_picture_with_text_overlay_widget.h"
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QPainterPath>
|
||||
#include <QStylePainter>
|
||||
#include <QTextOption>
|
||||
|
||||
/**
|
||||
* @brief Constructs a CardPictureWithTextOverlay widget.
|
||||
* @param parent The parent widget.
|
||||
* @param hoverToZoomEnabled If this widget will spawn a larger widget when hovered over.
|
||||
* @param raiseOnEnter If this widget will raise slightly when entered.
|
||||
* @param textColor The color of the overlay text.
|
||||
* @param outlineColor The color of the outline around the text.
|
||||
* @param fontSize The font size of the overlay text.
|
||||
* @param alignment The alignment of the text within the overlay.
|
||||
*
|
||||
* Sets the widget's size policy and default border style.
|
||||
*/
|
||||
CardInfoPictureWithTextOverlayWidget::CardInfoPictureWithTextOverlayWidget(QWidget *parent,
|
||||
const bool hoverToZoomEnabled,
|
||||
const bool raiseOnEnter,
|
||||
const QColor &textColor,
|
||||
const QColor &outlineColor,
|
||||
const int fontSize,
|
||||
const Qt::Alignment alignment)
|
||||
: CardInfoPictureWidget(parent, hoverToZoomEnabled, raiseOnEnter), textColor(textColor), outlineColor(outlineColor),
|
||||
fontSize(fontSize), textAlignment(alignment)
|
||||
{
|
||||
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the overlay text to be displayed on the card.
|
||||
* @param text The text to overlay.
|
||||
*
|
||||
* Updates the widget to display the new overlay text.
|
||||
*/
|
||||
void CardInfoPictureWithTextOverlayWidget::setOverlayText(const QString &text)
|
||||
{
|
||||
overlayText = text;
|
||||
update(); // Trigger a redraw to display the updated text
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the color of the overlay text.
|
||||
* @param color The new text color.
|
||||
*/
|
||||
void CardInfoPictureWithTextOverlayWidget::setTextColor(const QColor &color)
|
||||
{
|
||||
textColor = color;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the outline color around the overlay text.
|
||||
* @param color The new outline color.
|
||||
*/
|
||||
void CardInfoPictureWithTextOverlayWidget::setOutlineColor(const QColor &color)
|
||||
{
|
||||
outlineColor = color;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the font size for the overlay text.
|
||||
* @param size The new font size.
|
||||
*/
|
||||
void CardInfoPictureWithTextOverlayWidget::setFontSize(const int size)
|
||||
{
|
||||
fontSize = size > 0 ? size : 1;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the alignment of the overlay text within the widget.
|
||||
* @param alignment The new text alignment.
|
||||
*/
|
||||
void CardInfoPictureWithTextOverlayWidget::setTextAlignment(const Qt::Alignment alignment)
|
||||
{
|
||||
textAlignment = alignment;
|
||||
update();
|
||||
}
|
||||
|
||||
void CardInfoPictureWithTextOverlayWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
emit imageClicked(event, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Paints the widget, including both the card image and the text overlay.
|
||||
* @param event The paint event.
|
||||
*
|
||||
* Draws the card image first, then overlays text on top. The text is wrapped and centered within the image.
|
||||
*/
|
||||
void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
// Call the base class's paintEvent to draw the card image
|
||||
CardInfoPictureWidget::paintEvent(event);
|
||||
|
||||
// If no overlay text, skip drawing the text
|
||||
if (overlayText.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QStylePainter painter(this);
|
||||
|
||||
// Get the pixmap from the base class using the getter
|
||||
const QPixmap &pixmap = getResizedPixmap();
|
||||
if (pixmap.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate size and position for drawing
|
||||
const QSize scaledSize = pixmap.size().scaled(size(), Qt::KeepAspectRatio);
|
||||
const QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2};
|
||||
const QRect pixmapRect(topLeft, scaledSize);
|
||||
|
||||
// Calculate the optimal font size
|
||||
QFont font = painter.font();
|
||||
int optimalFontSize = fontSize; // Start with the user-defined font size
|
||||
const QFontMetrics baseMetrics(font);
|
||||
int textWidth = pixmapRect.width();
|
||||
|
||||
// Reduce the font size until the text fits within the pixmap's width
|
||||
do {
|
||||
font.setPointSize(optimalFontSize);
|
||||
QFontMetrics fm(font);
|
||||
int currentWidth = 0;
|
||||
for (const QString &word : overlayText.split(' ')) {
|
||||
currentWidth = std::max(currentWidth, fm.horizontalAdvance(word));
|
||||
}
|
||||
|
||||
if (currentWidth <= textWidth) {
|
||||
break;
|
||||
}
|
||||
|
||||
--optimalFontSize;
|
||||
} while (optimalFontSize > 1);
|
||||
|
||||
// Apply the calculated font size
|
||||
painter.setFont(font);
|
||||
|
||||
// Wrap the text to fit within the pixmap width
|
||||
const QFontMetrics fontMetrics(font);
|
||||
QString wrappedText;
|
||||
QString currentLine;
|
||||
QStringList words = overlayText.split(' ');
|
||||
for (const QString &word : words) {
|
||||
if (fontMetrics.horizontalAdvance(currentLine + " " + word) > textWidth) {
|
||||
wrappedText += currentLine + '\n';
|
||||
currentLine = word;
|
||||
} else {
|
||||
if (!currentLine.isEmpty()) {
|
||||
currentLine += " ";
|
||||
}
|
||||
currentLine += word;
|
||||
}
|
||||
}
|
||||
wrappedText += currentLine;
|
||||
|
||||
// Calculate total text block height
|
||||
int totalTextHeight = wrappedText.count('\n') * fontMetrics.height() + fontMetrics.height();
|
||||
|
||||
// Adjust font size if the total text height exceeds the pixmap height
|
||||
while (totalTextHeight > pixmapRect.height() && optimalFontSize > 1) {
|
||||
--optimalFontSize;
|
||||
font.setPointSize(optimalFontSize);
|
||||
painter.setFont(font);
|
||||
const QFontMetrics newMetrics(font);
|
||||
totalTextHeight = wrappedText.count('\n') * newMetrics.height() + newMetrics.height();
|
||||
}
|
||||
|
||||
// Set up the text layout options
|
||||
QTextOption textOption;
|
||||
textOption.setAlignment(textAlignment);
|
||||
|
||||
// Create a text rectangle centered vertically within the pixmap rect
|
||||
auto textRect = QRect(pixmapRect.left(), pixmapRect.top(), pixmapRect.width(), totalTextHeight);
|
||||
textRect.moveTop((pixmapRect.height() - totalTextHeight) / 2 + pixmapRect.top());
|
||||
|
||||
// Draw the outlined text
|
||||
drawOutlinedText(painter, textRect, wrappedText, textOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draws text with an outline for visibility.
|
||||
* @param painter The painter to draw the text.
|
||||
* @param textRect The rectangle area to draw the text in.
|
||||
* @param text The text to display.
|
||||
* @param textOption The text layout options, such as alignment.
|
||||
*
|
||||
* Draws an outline around the text to enhance readability before drawing the main text.
|
||||
*/
|
||||
void CardInfoPictureWithTextOverlayWidget::drawOutlinedText(QPainter &painter,
|
||||
const QRect &textRect,
|
||||
const QString &text,
|
||||
const QTextOption &textOption) const
|
||||
{
|
||||
painter.setPen(outlineColor);
|
||||
for (int dx = -1; dx <= 1; ++dx) {
|
||||
for (int dy = -1; dy <= 1; ++dy) {
|
||||
if (dx != 0 || dy != 0) {
|
||||
QRect shiftedTextRect = textRect.translated(dx, dy);
|
||||
painter.drawText(shiftedTextRect, text, textOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the main text
|
||||
painter.setPen(textColor);
|
||||
painter.drawText(textRect, text, textOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides the recommended size for this widget.
|
||||
* @return The suggested widget size.
|
||||
*/
|
||||
QSize CardInfoPictureWithTextOverlayWidget::sizeHint() const
|
||||
{
|
||||
return CardInfoPictureWidget::sizeHint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides the minimum recommended size for this widget.
|
||||
* @return The minimum widget size.
|
||||
*/
|
||||
QSize CardInfoPictureWithTextOverlayWidget::minimumSizeHint() const
|
||||
{
|
||||
// Same as sizeHint, but ensure that there is at least some space for the pixmap
|
||||
const QPixmap &pixmap = getResizedPixmap();
|
||||
const QSize pixmapSize = pixmap.isNull() ? QSize(0, 0) : pixmap.size();
|
||||
|
||||
// Get the font metrics for the overlay text
|
||||
QFont font;
|
||||
font.setPointSize(fontSize);
|
||||
const QFontMetrics fontMetrics(font);
|
||||
|
||||
// Calculate the height required for the text
|
||||
const QStringList lines = overlayText.split('\n');
|
||||
const int totalTextHeight = static_cast<int>(lines.size()) * fontMetrics.height();
|
||||
|
||||
// Return the maximum width and combined height
|
||||
return {pixmapSize.width(), pixmapSize.height() + totalTextHeight};
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
#ifndef CARD_PICTURE_WITH_TEXT_OVERLAY_H
|
||||
#define CARD_PICTURE_WITH_TEXT_OVERLAY_H
|
||||
|
||||
#include "card_info_picture_widget.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QSize>
|
||||
#include <QTextOption>
|
||||
|
||||
class CardInfoPictureWithTextOverlayWidget : public CardInfoPictureWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CardInfoPictureWithTextOverlayWidget(QWidget *parent = nullptr,
|
||||
bool hoverToZoomEnabled = false,
|
||||
bool raiseOnEnter = false,
|
||||
const QColor &textColor = Qt::white,
|
||||
const QColor &outlineColor = Qt::black,
|
||||
int fontSize = 12,
|
||||
Qt::Alignment alignment = Qt::AlignCenter);
|
||||
|
||||
void setOverlayText(const QString &text);
|
||||
void setTextColor(const QColor &color);
|
||||
void setOutlineColor(const QColor &color);
|
||||
void setFontSize(int size);
|
||||
void setTextAlignment(Qt::Alignment alignment);
|
||||
|
||||
[[nodiscard]] QSize sizeHint() const override;
|
||||
signals:
|
||||
void imageClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
[[nodiscard]] QSize minimumSizeHint() const override;
|
||||
|
||||
private:
|
||||
void drawOutlinedText(QPainter &painter,
|
||||
const QRect &textRect,
|
||||
const QString &text,
|
||||
const QTextOption &textOption) const;
|
||||
|
||||
QString overlayText;
|
||||
QColor textColor;
|
||||
QColor outlineColor;
|
||||
int fontSize;
|
||||
Qt::Alignment textAlignment;
|
||||
};
|
||||
|
||||
#endif // CARD_PICTURE_WITH_TEXT_OVERLAY_H
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
#include "card_info_text_widget.h"
|
||||
|
||||
#include "../../../card/game_specific_terms.h"
|
||||
#include "../../../game/board/card_item.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QTextEdit>
|
||||
|
||||
CardInfoTextWidget::CardInfoTextWidget(QWidget *parent) : QFrame(parent), info(nullptr)
|
||||
{
|
||||
nameLabel = new QLabel;
|
||||
nameLabel->setOpenExternalLinks(false);
|
||||
nameLabel->setWordWrap(true);
|
||||
connect(nameLabel, SIGNAL(linkActivated(const QString &)), this, SIGNAL(linkActivated(const QString &)));
|
||||
|
||||
textLabel = new QTextEdit();
|
||||
textLabel->setReadOnly(true);
|
||||
|
||||
auto *grid = new QGridLayout(this);
|
||||
grid->addWidget(nameLabel, 0, 0);
|
||||
grid->addWidget(textLabel, 1, 0, -1, 2);
|
||||
grid->setRowStretch(1, 1);
|
||||
grid->setColumnStretch(1, 1);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void CardInfoTextWidget::setCard(CardInfoPtr card)
|
||||
{
|
||||
if (card == nullptr) {
|
||||
nameLabel->setText("");
|
||||
textLabel->setText("");
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = "<table width=\"100%\" border=0 cellspacing=0 cellpadding=0>";
|
||||
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>%2</td></tr>")
|
||||
.arg(tr("Name:"), card->getName().toHtmlEscaped());
|
||||
|
||||
QStringList cardProps = card->getProperties();
|
||||
for (const QString &key : cardProps) {
|
||||
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());
|
||||
}
|
||||
|
||||
auto relatedCards = card->getAllRelatedCards();
|
||||
if (!relatedCards.empty()) {
|
||||
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>").arg(tr("Related cards:"));
|
||||
|
||||
for (auto *relatedCard : relatedCards) {
|
||||
QString tmp = relatedCard->getName().toHtmlEscaped();
|
||||
text += "<a href=\"" + tmp + "\">" + tmp + "</a><br>";
|
||||
}
|
||||
|
||||
text += "</td></tr>";
|
||||
}
|
||||
|
||||
text += "</table>";
|
||||
nameLabel->setText(text);
|
||||
textLabel->setText(card->getText());
|
||||
}
|
||||
|
||||
void CardInfoTextWidget::setInvalidCardName(const QString &cardName)
|
||||
{
|
||||
nameLabel->setText(tr("Unknown card:") + " " + cardName);
|
||||
textLabel->setText("");
|
||||
}
|
||||
|
||||
void CardInfoTextWidget::retranslateUi()
|
||||
{
|
||||
/*
|
||||
* There's no way we can really translate the text currently being rendered.
|
||||
* The best we can do is invalidate the current text.
|
||||
*/
|
||||
setInvalidCardName("");
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef CARDINFOTEXT_H
|
||||
#define CARDINFOTEXT_H
|
||||
|
||||
#include "../../../card/card_info.h"
|
||||
|
||||
#include <QFrame>
|
||||
class QLabel;
|
||||
class QTextEdit;
|
||||
|
||||
class CardInfoTextWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QLabel *nameLabel;
|
||||
QTextEdit *textLabel;
|
||||
CardInfoPtr info;
|
||||
|
||||
public:
|
||||
explicit CardInfoTextWidget(QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
void setInvalidCardName(const QString &cardName);
|
||||
|
||||
signals:
|
||||
void linkActivated(const QString &link);
|
||||
public slots:
|
||||
void setCard(CardInfoPtr card);
|
||||
};
|
||||
|
||||
#endif
|
||||
61
cockatrice/src/interface/widgets/cards/card_size_widget.cpp
Normal file
61
cockatrice/src/interface/widgets/cards/card_size_widget.cpp
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#include "card_size_widget.h"
|
||||
|
||||
#include "../../../settings/cache_settings.h"
|
||||
#include "../printing_selector/printing_selector.h"
|
||||
#include "../visual_deck_storage/visual_deck_storage_widget.h"
|
||||
|
||||
/**
|
||||
* @class CardSizeWidget
|
||||
* @brief A widget for adjusting card sizes using a slider.
|
||||
*
|
||||
* This widget allows users to dynamically change the card size in a linked FlowWidget
|
||||
* and updates the application's settings accordingly.
|
||||
*/
|
||||
CardSizeWidget::CardSizeWidget(QWidget *parent, FlowWidget *_flowWidget, int defaultValue)
|
||||
: parent(parent), flowWidget(_flowWidget)
|
||||
{
|
||||
cardSizeLayout = new QHBoxLayout(this);
|
||||
cardSizeLayout->setContentsMargins(9, 0, 9, 0);
|
||||
setLayout(cardSizeLayout);
|
||||
|
||||
cardSizeLabel = new QLabel(tr("Card Size"), this);
|
||||
cardSizeLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
|
||||
cardSizeSlider = new QSlider(Qt::Horizontal, this);
|
||||
cardSizeSlider->setRange(50, 250); ///< Slider range for card size adjustment.
|
||||
cardSizeSlider->setValue(defaultValue); ///< Initial slider value.
|
||||
|
||||
cardSizeLayout->addWidget(cardSizeLabel);
|
||||
cardSizeLayout->addWidget(cardSizeSlider);
|
||||
|
||||
if (flowWidget != nullptr) {
|
||||
connect(cardSizeSlider, &QSlider::valueChanged, flowWidget, &FlowWidget::setMinimumSizeToMaxSizeHint);
|
||||
}
|
||||
|
||||
// Debounce setup
|
||||
debounceTimer.setSingleShot(true);
|
||||
connect(&debounceTimer, &QTimer::timeout, this, [this] { emit cardSizeSettingUpdated(pendingValue); });
|
||||
|
||||
connect(cardSizeSlider, &QSlider::valueChanged, this, &CardSizeWidget::updateCardSizeSetting);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the card size setting in the application's cache.
|
||||
*
|
||||
* @param newValue The new card size value set by the slider.
|
||||
*/
|
||||
void CardSizeWidget::updateCardSizeSetting(int newValue)
|
||||
{
|
||||
pendingValue = newValue;
|
||||
debounceTimer.start(300); // 300ms debounce time
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the slider widget used for adjusting the card size.
|
||||
*
|
||||
* @return A pointer to the QSlider object.
|
||||
*/
|
||||
QSlider *CardSizeWidget::getSlider() const
|
||||
{
|
||||
return cardSizeSlider;
|
||||
}
|
||||
41
cockatrice/src/interface/widgets/cards/card_size_widget.h
Normal file
41
cockatrice/src/interface/widgets/cards/card_size_widget.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef CARD_SIZE_WIDGET_H
|
||||
#define CARD_SIZE_WIDGET_H
|
||||
|
||||
#include "../general/layout_containers/flow_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QSlider>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
class CardSizeWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CardSizeWidget(QWidget *parent, FlowWidget *flowWidget = nullptr, int defaultValue = 100);
|
||||
[[nodiscard]] QSlider *getSlider() const;
|
||||
|
||||
private slots:
|
||||
void updateCardSizeSetting(int newValue);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* Emitted when the slider value changes, but on a debounce timer.
|
||||
* Any parents that care about saving the value to settings should use this signal to indicate when to save the new
|
||||
* value to settings.
|
||||
*/
|
||||
void cardSizeSettingUpdated(int newValue);
|
||||
|
||||
private:
|
||||
QWidget *parent;
|
||||
FlowWidget *flowWidget;
|
||||
QHBoxLayout *cardSizeLayout;
|
||||
QLabel *cardSizeLabel;
|
||||
QSlider *cardSizeSlider;
|
||||
QTimer debounceTimer; // Debounce timer
|
||||
int pendingValue; // Stores the latest slider value
|
||||
};
|
||||
|
||||
#endif // CARD_SIZE_WIDGET_H
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
#include "deck_card_zone_display_widget.h"
|
||||
|
||||
#include "../../../deck/deck_list_model.h"
|
||||
#include "../../../utility/card_info_comparator.h"
|
||||
#include "card_group_display_widgets/flat_card_group_display_widget.h"
|
||||
#include "card_group_display_widgets/overlapped_card_group_display_widget.h"
|
||||
|
||||
#include <QResizeEvent>
|
||||
|
||||
DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent,
|
||||
DeckListModel *_deckListModel,
|
||||
QPersistentModelIndex _trackedIndex,
|
||||
QString _zoneName,
|
||||
QString _activeGroupCriteria,
|
||||
QStringList _activeSortCriteria,
|
||||
DisplayType _displayType,
|
||||
int bannerOpacity,
|
||||
int subBannerOpacity,
|
||||
CardSizeWidget *_cardSizeWidget)
|
||||
: QWidget(parent), deckListModel(_deckListModel), trackedIndex(_trackedIndex), zoneName(_zoneName),
|
||||
activeGroupCriteria(_activeGroupCriteria), activeSortCriteria(_activeSortCriteria), displayType(_displayType),
|
||||
bannerOpacity(bannerOpacity), subBannerOpacity(subBannerOpacity), cardSizeWidget(_cardSizeWidget)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
banner = new BannerWidget(this, zoneName, Qt::Orientation::Vertical, bannerOpacity);
|
||||
layout->addWidget(banner);
|
||||
|
||||
cardGroupContainer = new QWidget(this);
|
||||
cardGroupLayout = new QVBoxLayout(cardGroupContainer);
|
||||
cardGroupContainer->setLayout(cardGroupLayout);
|
||||
layout->addWidget(cardGroupContainer);
|
||||
|
||||
banner->setBuddy(cardGroupContainer);
|
||||
|
||||
displayCards();
|
||||
|
||||
connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &DeckCardZoneDisplayWidget::onCategoryAddition);
|
||||
connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &DeckCardZoneDisplayWidget::onCategoryRemoval);
|
||||
}
|
||||
|
||||
void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget)
|
||||
{
|
||||
cardGroupLayout->removeWidget(displayWidget);
|
||||
displayWidget->setParent(nullptr);
|
||||
for (auto idx : indexToWidgetMap.keys()) {
|
||||
if (!idx.isValid()) {
|
||||
indexToWidgetMap.remove(idx);
|
||||
}
|
||||
}
|
||||
delete displayWidget;
|
||||
}
|
||||
|
||||
void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index)
|
||||
{
|
||||
auto categoryName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString();
|
||||
if (indexToWidgetMap.contains(index)) {
|
||||
return;
|
||||
}
|
||||
if (displayType == DisplayType::Overlap) {
|
||||
auto *displayWidget = new OverlappedCardGroupDisplayWidget(
|
||||
cardGroupContainer, deckListModel, index, zoneName, categoryName, activeGroupCriteria, activeSortCriteria,
|
||||
subBannerOpacity, cardSizeWidget);
|
||||
connect(displayWidget, &OverlappedCardGroupDisplayWidget::cardClicked, this,
|
||||
&DeckCardZoneDisplayWidget::onClick);
|
||||
connect(displayWidget, &OverlappedCardGroupDisplayWidget::cardHovered, this,
|
||||
&DeckCardZoneDisplayWidget::onHover);
|
||||
connect(displayWidget, &CardGroupDisplayWidget::cleanupRequested, this,
|
||||
&DeckCardZoneDisplayWidget::cleanupInvalidCardGroup);
|
||||
connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, displayWidget,
|
||||
&CardGroupDisplayWidget::onActiveSortCriteriaChanged);
|
||||
cardGroupLayout->addWidget(displayWidget);
|
||||
indexToWidgetMap.insert(index, displayWidget);
|
||||
} else if (displayType == DisplayType::Flat) {
|
||||
auto *displayWidget =
|
||||
new FlatCardGroupDisplayWidget(cardGroupContainer, deckListModel, index, zoneName, categoryName,
|
||||
activeGroupCriteria, activeSortCriteria, subBannerOpacity, cardSizeWidget);
|
||||
connect(displayWidget, &FlatCardGroupDisplayWidget::cardClicked, this, &DeckCardZoneDisplayWidget::onClick);
|
||||
connect(displayWidget, &FlatCardGroupDisplayWidget::cardHovered, this, &DeckCardZoneDisplayWidget::onHover);
|
||||
connect(displayWidget, &CardGroupDisplayWidget::cleanupRequested, this,
|
||||
&DeckCardZoneDisplayWidget::cleanupInvalidCardGroup);
|
||||
connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, displayWidget,
|
||||
&CardGroupDisplayWidget::onActiveSortCriteriaChanged);
|
||||
cardGroupLayout->addWidget(displayWidget);
|
||||
indexToWidgetMap.insert(index, displayWidget);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckCardZoneDisplayWidget::displayCards()
|
||||
{
|
||||
QSortFilterProxyModel proxy;
|
||||
proxy.setSourceModel(deckListModel);
|
||||
proxy.setSortRole(Qt::EditRole);
|
||||
proxy.sort(1, Qt::AscendingOrder);
|
||||
|
||||
// 1. trackedIndex is a source index → map it to proxy space
|
||||
QModelIndex proxyParent = proxy.mapFromSource(trackedIndex);
|
||||
|
||||
// 2. iterate children under the proxy parent
|
||||
for (int i = 0; i < proxy.rowCount(proxyParent); ++i) {
|
||||
QModelIndex proxyIndex = proxy.index(i, 0, proxyParent);
|
||||
|
||||
// 3. map back to source
|
||||
QModelIndex sourceIndex = proxy.mapToSource(proxyIndex);
|
||||
|
||||
// 4. persist the source index
|
||||
QPersistentModelIndex persistent(sourceIndex);
|
||||
|
||||
constructAppropriateWidget(persistent);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckCardZoneDisplayWidget::onCategoryAddition(const QModelIndex &parent, int first, int last)
|
||||
{
|
||||
if (!trackedIndex.isValid()) {
|
||||
emit requestCleanup(this);
|
||||
return;
|
||||
}
|
||||
if (parent == trackedIndex) {
|
||||
for (int i = first; i <= last; i++) {
|
||||
QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(i, 0, trackedIndex));
|
||||
|
||||
constructAppropriateWidget(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeckCardZoneDisplayWidget::onCategoryRemoval(const QModelIndex &parent, int first, int last)
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
Q_UNUSED(first);
|
||||
Q_UNUSED(last);
|
||||
for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) {
|
||||
if (!idx.isValid()) {
|
||||
cardGroupLayout->removeWidget(indexToWidgetMap.value(idx));
|
||||
indexToWidgetMap.value(idx)->deleteLater();
|
||||
indexToWidgetMap.remove(idx);
|
||||
}
|
||||
}
|
||||
if (!trackedIndex.isValid()) {
|
||||
emit requestCleanup(this);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckCardZoneDisplayWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
for (QObject *child : layout->children()) {
|
||||
QWidget *widget = qobject_cast<QWidget *>(child);
|
||||
if (widget) {
|
||||
widget->setMaximumWidth(width());
|
||||
}
|
||||
}
|
||||
}
|
||||
void DeckCardZoneDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
|
||||
{
|
||||
emit cardClicked(event, card, zoneName);
|
||||
}
|
||||
void DeckCardZoneDisplayWidget::onHover(const ExactCard &card)
|
||||
{
|
||||
emit cardHovered(card);
|
||||
}
|
||||
|
||||
void DeckCardZoneDisplayWidget::refreshDisplayType(const DisplayType &_displayType)
|
||||
{
|
||||
displayType = _displayType;
|
||||
QLayoutItem *item;
|
||||
while ((item = cardGroupLayout->takeAt(0)) != nullptr) {
|
||||
if (item->widget()) {
|
||||
item->widget()->deleteLater();
|
||||
} else if (item->layout()) {
|
||||
item->layout()->deleteLater();
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
|
||||
indexToWidgetMap.clear();
|
||||
|
||||
// We gotta wait for all the deleteLater's to finish so we fire after the next event cycle
|
||||
|
||||
auto timer = new QTimer(this);
|
||||
timer->setSingleShot(true);
|
||||
connect(timer, &QTimer::timeout, this, [this]() { displayCards(); });
|
||||
timer->start();
|
||||
}
|
||||
|
||||
void DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged(QString _activeGroupCriteria)
|
||||
{
|
||||
activeGroupCriteria = _activeGroupCriteria;
|
||||
displayCards();
|
||||
}
|
||||
|
||||
void DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSortCriteria)
|
||||
{
|
||||
activeSortCriteria = _activeSortCriteria;
|
||||
emit activeSortCriteriaChanged(activeSortCriteria);
|
||||
}
|
||||
|
||||
QList<QString> DeckCardZoneDisplayWidget::getGroupCriteriaValueList()
|
||||
{
|
||||
QList<QString> groupCriteriaValues;
|
||||
|
||||
QList<ExactCard> cardsInZone = deckListModel->getCardsForZone(zoneName);
|
||||
|
||||
for (const ExactCard &cardInZone : cardsInZone) {
|
||||
groupCriteriaValues.append(cardInZone.getInfo().getProperty(activeGroupCriteria));
|
||||
}
|
||||
|
||||
groupCriteriaValues.removeDuplicates();
|
||||
groupCriteriaValues.sort();
|
||||
|
||||
return groupCriteriaValues;
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
#ifndef DECK_CARD_ZONE_DISPLAY_WIDGET_H
|
||||
#define DECK_CARD_ZONE_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../card/card_info.h"
|
||||
#include "../../../deck/deck_list_model.h"
|
||||
#include "../general/display/banner_widget.h"
|
||||
#include "../general/layout_containers/overlap_widget.h"
|
||||
#include "../visual_deck_editor/visual_deck_editor_widget.h"
|
||||
#include "card_group_display_widgets/card_group_display_widget.h"
|
||||
#include "card_info_picture_with_text_overlay_widget.h"
|
||||
#include "card_size_widget.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class DeckCardZoneDisplayWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeckCardZoneDisplayWidget(QWidget *parent,
|
||||
DeckListModel *deckListModel,
|
||||
QPersistentModelIndex trackedIndex,
|
||||
QString zoneName,
|
||||
QString activeGroupCriteria,
|
||||
QStringList activeSortCriteria,
|
||||
DisplayType displayType,
|
||||
int bannerOpacity,
|
||||
int subBannerOpacity,
|
||||
CardSizeWidget *_cardSizeWidget);
|
||||
DeckListModel *deckListModel;
|
||||
QPersistentModelIndex trackedIndex;
|
||||
QString zoneName;
|
||||
void addCardsToOverlapWidget();
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
public slots:
|
||||
void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card);
|
||||
void onHover(const ExactCard &card);
|
||||
void cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget);
|
||||
void constructAppropriateWidget(QPersistentModelIndex index);
|
||||
void displayCards();
|
||||
void refreshDisplayType(const DisplayType &displayType);
|
||||
void onActiveGroupCriteriaChanged(QString activeGroupCriteria);
|
||||
void onActiveSortCriteriaChanged(QStringList activeSortCriteria);
|
||||
QList<QString> getGroupCriteriaValueList();
|
||||
void onCategoryAddition(const QModelIndex &parent, int first, int last);
|
||||
void onCategoryRemoval(const QModelIndex &parent, int first, int last);
|
||||
|
||||
signals:
|
||||
void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card, QString zoneName);
|
||||
void cardHovered(const ExactCard &card);
|
||||
void activeSortCriteriaChanged(QStringList activeSortCriteria);
|
||||
void requestCleanup(DeckCardZoneDisplayWidget *displayWidget);
|
||||
|
||||
private:
|
||||
QString activeGroupCriteria;
|
||||
QStringList activeSortCriteria;
|
||||
DisplayType displayType = DisplayType::Overlap;
|
||||
int bannerOpacity = 20;
|
||||
int subBannerOpacity = 10;
|
||||
CardSizeWidget *cardSizeWidget;
|
||||
QVBoxLayout *layout;
|
||||
BannerWidget *banner;
|
||||
QWidget *cardGroupContainer;
|
||||
QVBoxLayout *cardGroupLayout;
|
||||
OverlapWidget *overlapWidget;
|
||||
QHash<QPersistentModelIndex, QWidget *> indexToWidgetMap;
|
||||
};
|
||||
|
||||
#endif // DECK_CARD_ZONE_DISPLAY_WIDGET_H
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
#include "deck_preview_card_picture_widget.h"
|
||||
|
||||
#include "../../../settings/cache_settings.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QFontMetrics>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainterPath>
|
||||
#include <QStylePainter>
|
||||
#include <QTextOption>
|
||||
|
||||
/**
|
||||
* @brief Constructs a CardPictureWithTextOverlay widget.
|
||||
* @param parent The parent widget.
|
||||
* @param hoverToZoomEnabled If this widget will spawn a larger widget when hovered over.
|
||||
* @param textColor The color of the overlay text.
|
||||
* @param outlineColor The color of the outline around the text.
|
||||
* @param fontSize The font size of the overlay text.
|
||||
* @param alignment The alignment of the text within the overlay.
|
||||
* @param _deckLoader The Deck Loader holding the Deck associated with this preview.
|
||||
*
|
||||
* Sets the widget's size policy and default border style.
|
||||
*/
|
||||
DeckPreviewCardPictureWidget::DeckPreviewCardPictureWidget(QWidget *parent,
|
||||
const bool hoverToZoomEnabled,
|
||||
const bool raiseOnEnter,
|
||||
const QColor &textColor,
|
||||
const QColor &outlineColor,
|
||||
const int fontSize,
|
||||
const Qt::Alignment alignment)
|
||||
: CardInfoPictureWithTextOverlayWidget(parent,
|
||||
hoverToZoomEnabled,
|
||||
raiseOnEnter,
|
||||
textColor,
|
||||
outlineColor,
|
||||
fontSize,
|
||||
alignment)
|
||||
{
|
||||
singleClickTimer = new QTimer(this);
|
||||
singleClickTimer->setSingleShot(true);
|
||||
connect(singleClickTimer, &QTimer::timeout, this, [this]() { emit imageClicked(lastMouseEvent, this); });
|
||||
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageSelectionAnimationChanged, this,
|
||||
&CardInfoPictureWidget::setRaiseOnEnterEnabled);
|
||||
}
|
||||
|
||||
void DeckPreviewCardPictureWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
lastMouseEvent = event;
|
||||
singleClickTimer->start(QApplication::doubleClickInterval());
|
||||
} else {
|
||||
emit imageClicked(event, this);
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void DeckPreviewCardPictureWidget::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
singleClickTimer->stop(); // Prevent single-click logic
|
||||
emit imageDoubleClicked(lastMouseEvent, this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef DECK_PREVIEW_CARD_PICTURE_WIDGET_H
|
||||
#define DECK_PREVIEW_CARD_PICTURE_WIDGET_H
|
||||
|
||||
#include "card_info_picture_with_text_overlay_widget.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QSize>
|
||||
#include <QTextOption>
|
||||
|
||||
class DeckPreviewCardPictureWidget final : public CardInfoPictureWithTextOverlayWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeckPreviewCardPictureWidget(QWidget *parent,
|
||||
bool hoverToZoomEnabled = false,
|
||||
bool raiseOnEnter = false,
|
||||
const QColor &textColor = Qt::white,
|
||||
const QColor &outlineColor = Qt::black,
|
||||
int fontSize = 12,
|
||||
Qt::Alignment alignment = Qt::AlignCenter);
|
||||
|
||||
signals:
|
||||
void imageClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
|
||||
void imageDoubleClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
|
||||
|
||||
private:
|
||||
QTimer *singleClickTimer;
|
||||
QMouseEvent *lastMouseEvent = nullptr; // Store the last mouse event
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
};
|
||||
|
||||
#endif // DECK_PREVIEW_CARD_PICTURE_WIDGET_H
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#include "deck_analytics_widget.h"
|
||||
|
||||
DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckListModel)
|
||||
: QWidget(parent), deckListModel(_deckListModel)
|
||||
{
|
||||
mainLayout = new QVBoxLayout();
|
||||
setLayout(mainLayout);
|
||||
|
||||
scrollArea = new QScrollArea(this);
|
||||
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
mainLayout->addWidget(scrollArea);
|
||||
|
||||
container = new QWidget(scrollArea);
|
||||
containerLayout = new QVBoxLayout(container);
|
||||
container->setLayout(containerLayout);
|
||||
scrollArea->setWidget(container);
|
||||
|
||||
manaCurveWidget = new ManaCurveWidget(this, deckListModel);
|
||||
containerLayout->addWidget(manaCurveWidget);
|
||||
|
||||
manaDevotionWidget = new ManaDevotionWidget(this, deckListModel);
|
||||
containerLayout->addWidget(manaDevotionWidget);
|
||||
|
||||
manaBaseWidget = new ManaBaseWidget(this, deckListModel);
|
||||
containerLayout->addWidget(manaBaseWidget);
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::refreshDisplays(DeckListModel *_deckModel)
|
||||
{
|
||||
deckListModel = _deckModel;
|
||||
manaCurveWidget->setDeckModel(_deckModel);
|
||||
manaDevotionWidget->setDeckModel(_deckModel);
|
||||
manaBaseWidget->setDeckModel(_deckModel);
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#ifndef DECK_ANALYTICS_WIDGET_H
|
||||
#define DECK_ANALYTICS_WIDGET_H
|
||||
|
||||
#include "../../../deck/deck_list_model.h"
|
||||
#include "../general/layout_containers/flow_widget.h"
|
||||
#include "mana_base_widget.h"
|
||||
#include "mana_curve_widget.h"
|
||||
#include "mana_devotion_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <deck_list.h>
|
||||
|
||||
class DeckAnalyticsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeckAnalyticsWidget(QWidget *parent, DeckListModel *deckListModel);
|
||||
void setDeckList(const DeckList &_deckListModel);
|
||||
std::map<int, int> analyzeManaCurve();
|
||||
void refreshDisplays(DeckListModel *_deckListModel);
|
||||
|
||||
private:
|
||||
DeckListModel *deckListModel;
|
||||
QVBoxLayout *mainLayout;
|
||||
|
||||
QWidget *container;
|
||||
QVBoxLayout *containerLayout;
|
||||
|
||||
QScrollArea *scrollArea;
|
||||
|
||||
ManaCurveWidget *manaCurveWidget;
|
||||
ManaDevotionWidget *manaDevotionWidget;
|
||||
ManaBaseWidget *manaBaseWidget;
|
||||
};
|
||||
|
||||
#endif // DECK_ANALYTICS_WIDGET_H
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
#include "mana_base_widget.h"
|
||||
|
||||
#include "../../../database/card_database.h"
|
||||
#include "../../../database/card_database_manager.h"
|
||||
#include "../../../deck/deck_loader.h"
|
||||
#include "../general/display/banner_widget.h"
|
||||
#include "../general/display/bar_widget.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QRegularExpression>
|
||||
#include <deck_list.h>
|
||||
|
||||
ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListModel *_deckListModel)
|
||||
: QWidget(parent), deckListModel(_deckListModel)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
bannerWidget = new BannerWidget(this, tr("Mana Base"), Qt::Vertical, 100);
|
||||
bannerWidget->setMaximumHeight(100);
|
||||
layout->addWidget(bannerWidget);
|
||||
|
||||
barContainer = new QWidget(this);
|
||||
barLayout = new QHBoxLayout(barContainer);
|
||||
layout->addWidget(barContainer);
|
||||
|
||||
connect(deckListModel, &DeckListModel::dataChanged, this, &ManaBaseWidget::analyzeManaBase);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ManaBaseWidget::retranslateUi()
|
||||
{
|
||||
bannerWidget->setText(tr("Mana Base"));
|
||||
}
|
||||
|
||||
void ManaBaseWidget::setDeckModel(DeckListModel *deckModel)
|
||||
{
|
||||
deckListModel = deckModel;
|
||||
connect(deckListModel, &DeckListModel::dataChanged, this, &ManaBaseWidget::analyzeManaBase);
|
||||
analyzeManaBase();
|
||||
}
|
||||
|
||||
void ManaBaseWidget::updateDisplay()
|
||||
{
|
||||
// Clear the layout first
|
||||
QLayoutItem *item;
|
||||
while ((item = barLayout->takeAt(0)) != nullptr) {
|
||||
item->widget()->deleteLater();
|
||||
delete item;
|
||||
}
|
||||
|
||||
int highestEntry = 0;
|
||||
for (auto entry : manaBaseMap) {
|
||||
if (entry > highestEntry) {
|
||||
highestEntry = entry;
|
||||
}
|
||||
}
|
||||
|
||||
// Define color mapping for mana types
|
||||
QHash<QString, QColor> manaColors;
|
||||
manaColors.insert("W", QColor(248, 231, 185));
|
||||
manaColors.insert("U", QColor(14, 104, 171));
|
||||
manaColors.insert("B", QColor(21, 11, 0));
|
||||
manaColors.insert("R", QColor(211, 32, 42));
|
||||
manaColors.insert("G", QColor(0, 115, 62));
|
||||
manaColors.insert("C", QColor(150, 150, 150));
|
||||
|
||||
for (auto manaColor : manaBaseMap.keys()) {
|
||||
QColor barColor = manaColors.value(manaColor, Qt::gray);
|
||||
BarWidget *barWidget = new BarWidget(QString(manaColor), manaBaseMap[manaColor], highestEntry, barColor, this);
|
||||
barLayout->addWidget(barWidget);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
QHash<QString, int> ManaBaseWidget::analyzeManaBase()
|
||||
{
|
||||
manaBaseMap.clear();
|
||||
InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot();
|
||||
for (int i = 0; i < listRoot->size(); i++) {
|
||||
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
|
||||
for (int j = 0; j < currentZone->size(); j++) {
|
||||
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
|
||||
if (!currentCard)
|
||||
continue;
|
||||
|
||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||
CardInfoPtr info = CardDatabaseManager::getInstance()->getCardInfo(currentCard->getName());
|
||||
if (info) {
|
||||
auto devotion = determineManaProduction(info->getText());
|
||||
mergeManaCounts(manaBaseMap, devotion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDisplay();
|
||||
return manaBaseMap;
|
||||
}
|
||||
|
||||
QHash<QString, int> ManaBaseWidget::determineManaProduction(const QString &rulesText)
|
||||
{
|
||||
QHash<QString, int> manaCounts = {{"W", 0}, {"U", 0}, {"B", 0}, {"R", 0}, {"G", 0}, {"C", 0}};
|
||||
|
||||
QString text = rulesText.toLower(); // Normalize case for matching
|
||||
|
||||
// Quick keyword-based checks for any color and colorless mana
|
||||
if (text.contains("{t}: add one mana of any color") || text.contains("add one mana of any color")) {
|
||||
for (const auto &color : {QStringLiteral("W"), QStringLiteral("U"), QStringLiteral("B"), QStringLiteral("R"),
|
||||
QStringLiteral("G")}) {
|
||||
manaCounts[color]++;
|
||||
}
|
||||
}
|
||||
if (text.contains("{t}: add {c}") || text.contains("add one colorless mana")) {
|
||||
manaCounts["C"]++;
|
||||
}
|
||||
|
||||
// Optimized regex for specific mana symbols
|
||||
static const QRegularExpression specificColorRegex(R"(\{T\}:\s*Add\s*\{([WUBRG])\})");
|
||||
QRegularExpressionMatch match = specificColorRegex.match(rulesText);
|
||||
if (match.hasMatch()) {
|
||||
manaCounts[match.captured(1)]++;
|
||||
}
|
||||
|
||||
return manaCounts;
|
||||
}
|
||||
|
||||
void ManaBaseWidget::mergeManaCounts(QHash<QString, int> &manaCounts1, const QHash<QString, int> &manaCounts2)
|
||||
{
|
||||
for (auto it = manaCounts2.constBegin(); it != manaCounts2.constEnd(); ++it) {
|
||||
manaCounts1[it.key()] += it.value();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef MANA_BASE_WIDGET_H
|
||||
#define MANA_BASE_WIDGET_H
|
||||
|
||||
#include "../../../deck/deck_list_model.h"
|
||||
#include "../general/display/banner_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <deck_list.h>
|
||||
#include <utility>
|
||||
|
||||
class ManaBaseWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ManaBaseWidget(QWidget *parent, DeckListModel *deckListModel);
|
||||
QHash<QString, int> analyzeManaBase();
|
||||
void updateDisplay();
|
||||
|
||||
QHash<QString, int> determineManaProduction(const QString &manaString);
|
||||
void mergeManaCounts(QHash<QString, int> &manaCounts1, const QHash<QString, int> &manaCounts2);
|
||||
|
||||
public slots:
|
||||
void setDeckModel(DeckListModel *deckModel);
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
DeckListModel *deckListModel;
|
||||
BannerWidget *bannerWidget;
|
||||
QHash<QString, int> manaBaseMap;
|
||||
QVBoxLayout *layout;
|
||||
QWidget *barContainer;
|
||||
QHBoxLayout *barLayout;
|
||||
};
|
||||
|
||||
#endif // MANA_BASE_WIDGET_H
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#include "mana_curve_widget.h"
|
||||
|
||||
#include "../../../database/card_database.h"
|
||||
#include "../../../database/card_database_manager.h"
|
||||
#include "../../../deck/deck_loader.h"
|
||||
#include "../../../main.h"
|
||||
#include "../general/display/banner_widget.h"
|
||||
#include "../general/display/bar_widget.h"
|
||||
|
||||
#include <deck_list.h>
|
||||
#include <unordered_map>
|
||||
|
||||
ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListModel *_deckListModel)
|
||||
: QWidget(parent), deckListModel(_deckListModel)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
bannerWidget = new BannerWidget(this, tr("Mana Curve"), Qt::Vertical, 100);
|
||||
bannerWidget->setMaximumHeight(100);
|
||||
layout->addWidget(bannerWidget);
|
||||
|
||||
barContainer = new QWidget(this);
|
||||
barLayout = new QHBoxLayout(barContainer);
|
||||
layout->addWidget(barContainer);
|
||||
|
||||
connect(deckListModel, &DeckListModel::dataChanged, this, &ManaCurveWidget::analyzeManaCurve);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ManaCurveWidget::retranslateUi()
|
||||
{
|
||||
bannerWidget->setText(tr("Mana Curve"));
|
||||
}
|
||||
|
||||
void ManaCurveWidget::setDeckModel(DeckListModel *deckModel)
|
||||
{
|
||||
deckListModel = deckModel;
|
||||
connect(deckListModel, &DeckListModel::dataChanged, this, &ManaCurveWidget::analyzeManaCurve);
|
||||
analyzeManaCurve();
|
||||
}
|
||||
|
||||
std::unordered_map<int, int> ManaCurveWidget::analyzeManaCurve()
|
||||
{
|
||||
manaCurveMap.clear();
|
||||
InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot();
|
||||
for (int i = 0; i < listRoot->size(); i++) {
|
||||
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
|
||||
for (int j = 0; j < currentZone->size(); j++) {
|
||||
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
|
||||
if (!currentCard)
|
||||
continue;
|
||||
|
||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||
CardInfoPtr info = CardDatabaseManager::getInstance()->getCardInfo(currentCard->getName());
|
||||
if (info) {
|
||||
int cmc = info->getCmc().toInt();
|
||||
manaCurveMap[cmc]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDisplay();
|
||||
|
||||
return manaCurveMap;
|
||||
}
|
||||
|
||||
void ManaCurveWidget::updateDisplay()
|
||||
{
|
||||
// Clear the layout first
|
||||
if (barLayout != nullptr) {
|
||||
QLayoutItem *item;
|
||||
while ((item = barLayout->takeAt(0)) != nullptr) {
|
||||
item->widget()->deleteLater();
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
int highestEntry = 0;
|
||||
for (const auto &entry : manaCurveMap) {
|
||||
if (entry.second > highestEntry) {
|
||||
highestEntry = entry.second;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert unordered_map to ordered map to ensure sorting by CMC
|
||||
std::map<int, int> sortedManaCurve(manaCurveMap.begin(), manaCurveMap.end());
|
||||
|
||||
// Add new widgets to the layout in sorted order
|
||||
for (const auto &entry : sortedManaCurve) {
|
||||
BarWidget *barWidget =
|
||||
new BarWidget(QString::number(entry.first), entry.second, highestEntry, QColor(122, 122, 122), this);
|
||||
barLayout->addWidget(barWidget);
|
||||
}
|
||||
|
||||
update(); // Update the widget display
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef MANA_CURVE_WIDGET_H
|
||||
#define MANA_CURVE_WIDGET_H
|
||||
|
||||
#include "../../../deck/deck_list_model.h"
|
||||
#include "../general/display/banner_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <unordered_map>
|
||||
|
||||
class ManaCurveWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ManaCurveWidget(QWidget *parent, DeckListModel *deckListModel);
|
||||
void updateDisplay();
|
||||
|
||||
public slots:
|
||||
void setDeckModel(DeckListModel *deckModel);
|
||||
std::unordered_map<int, int> analyzeManaCurve();
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
DeckListModel *deckListModel;
|
||||
std::unordered_map<int, int> manaCurveMap;
|
||||
QVBoxLayout *layout;
|
||||
BannerWidget *bannerWidget;
|
||||
QWidget *barContainer;
|
||||
QHBoxLayout *barLayout;
|
||||
};
|
||||
|
||||
#endif // MANA_CURVE_WIDGET_H
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
#include "mana_devotion_widget.h"
|
||||
|
||||
#include "../../../database/card_database.h"
|
||||
#include "../../../database/card_database_manager.h"
|
||||
#include "../../../deck/deck_loader.h"
|
||||
#include "../../../main.h"
|
||||
#include "../general/display/banner_widget.h"
|
||||
#include "../general/display/bar_widget.h"
|
||||
|
||||
#include <deck_list.h>
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListModel *_deckListModel)
|
||||
: QWidget(parent), deckListModel(_deckListModel)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
bannerWidget = new BannerWidget(this, tr("Mana Devotion"), Qt::Vertical, 100);
|
||||
bannerWidget->setMaximumHeight(100);
|
||||
layout->addWidget(bannerWidget);
|
||||
|
||||
barLayout = new QHBoxLayout();
|
||||
layout->addLayout(barLayout);
|
||||
|
||||
connect(deckListModel, &DeckListModel::dataChanged, this, &ManaDevotionWidget::analyzeManaDevotion);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ManaDevotionWidget::retranslateUi()
|
||||
{
|
||||
bannerWidget->setText(tr("Mana Devotion"));
|
||||
}
|
||||
|
||||
void ManaDevotionWidget::setDeckModel(DeckListModel *deckModel)
|
||||
{
|
||||
deckListModel = deckModel;
|
||||
connect(deckListModel, &DeckListModel::dataChanged, this, &ManaDevotionWidget::analyzeManaDevotion);
|
||||
analyzeManaDevotion();
|
||||
}
|
||||
|
||||
std::unordered_map<char, int> ManaDevotionWidget::analyzeManaDevotion()
|
||||
{
|
||||
manaDevotionMap.clear();
|
||||
InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot();
|
||||
for (int i = 0; i < listRoot->size(); i++) {
|
||||
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
|
||||
for (int j = 0; j < currentZone->size(); j++) {
|
||||
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
|
||||
if (!currentCard)
|
||||
continue;
|
||||
|
||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||
CardInfoPtr info = CardDatabaseManager::getInstance()->getCardInfo(currentCard->getName());
|
||||
if (info) {
|
||||
auto devotion = countManaSymbols(info->getManaCost());
|
||||
mergeManaCounts(manaDevotionMap, devotion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDisplay();
|
||||
return manaDevotionMap;
|
||||
}
|
||||
|
||||
void ManaDevotionWidget::updateDisplay()
|
||||
{
|
||||
// Clear the layout first
|
||||
QLayoutItem *item;
|
||||
while ((item = barLayout->takeAt(0)) != nullptr) {
|
||||
item->widget()->deleteLater();
|
||||
delete item;
|
||||
}
|
||||
|
||||
int highestEntry = 0;
|
||||
for (auto entry : manaDevotionMap) {
|
||||
if (highestEntry < entry.second) {
|
||||
highestEntry = entry.second;
|
||||
}
|
||||
}
|
||||
|
||||
// Define color mapping for devotion bars
|
||||
std::unordered_map<char, QColor> manaColors = {{'W', QColor(248, 231, 185)}, {'U', QColor(14, 104, 171)},
|
||||
{'B', QColor(21, 11, 0)}, {'R', QColor(211, 32, 42)},
|
||||
{'G', QColor(0, 115, 62)}, {'C', QColor(150, 150, 150)}};
|
||||
|
||||
for (auto entry : manaDevotionMap) {
|
||||
QColor barColor = manaColors.count(entry.first) ? manaColors[entry.first] : Qt::gray;
|
||||
BarWidget *barWidget = new BarWidget(QString(entry.first), entry.second, highestEntry, barColor, this);
|
||||
barLayout->addWidget(barWidget);
|
||||
}
|
||||
|
||||
update(); // Update the widget display
|
||||
}
|
||||
|
||||
std::unordered_map<char, int> ManaDevotionWidget::countManaSymbols(const QString &manaString)
|
||||
{
|
||||
std::unordered_map<char, int> manaCounts = {{'W', 0}, {'U', 0}, {'B', 0}, {'R', 0}, {'G', 0}};
|
||||
|
||||
int len = manaString.length();
|
||||
for (int i = 0; i < len; ++i) {
|
||||
if (manaString[i] == '{') {
|
||||
++i; // Move past '{'
|
||||
if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) {
|
||||
char mana1 = manaString[i].toLatin1();
|
||||
++i; // Move to next character
|
||||
if (i < len && manaString[i] == '/') {
|
||||
++i; // Move past '/'
|
||||
if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) {
|
||||
char mana2 = manaString[i].toLatin1();
|
||||
manaCounts[mana1]++;
|
||||
manaCounts[mana2]++;
|
||||
} else {
|
||||
// Handle cases like "{W/}" where second part is invalid
|
||||
manaCounts[mana1]++;
|
||||
}
|
||||
} else {
|
||||
manaCounts[mana1]++;
|
||||
}
|
||||
}
|
||||
// Ensure we always skip to the closing '}'
|
||||
while (i < len && manaString[i] != '}') {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
// Check if the character is a standalone mana symbol (not inside {})
|
||||
else if (manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) {
|
||||
manaCounts[manaString[i].toLatin1()]++;
|
||||
}
|
||||
}
|
||||
|
||||
return manaCounts;
|
||||
}
|
||||
|
||||
void ManaDevotionWidget::mergeManaCounts(std::unordered_map<char, int> &manaCounts1,
|
||||
const std::unordered_map<char, int> &manaCounts2)
|
||||
{
|
||||
for (const auto &pair : manaCounts2) {
|
||||
manaCounts1[pair.first] += pair.second; // Add values for matching keys
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef MANA_DEVOTION_WIDGET_H
|
||||
#define MANA_DEVOTION_WIDGET_H
|
||||
|
||||
#include "../../../deck/deck_list_model.h"
|
||||
#include "../general/display/banner_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <deck_list.h>
|
||||
#include <utility>
|
||||
|
||||
class ManaDevotionWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ManaDevotionWidget(QWidget *parent, DeckListModel *deckListModel);
|
||||
void updateDisplay();
|
||||
|
||||
std::unordered_map<char, int> countManaSymbols(const QString &manaString);
|
||||
void mergeManaCounts(std::unordered_map<char, int> &manaCounts1, const std::unordered_map<char, int> &manaCounts2);
|
||||
|
||||
public slots:
|
||||
void setDeckModel(DeckListModel *deckModel);
|
||||
std::unordered_map<char, int> analyzeManaDevotion();
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
DeckListModel *deckListModel;
|
||||
BannerWidget *bannerWidget;
|
||||
std::unordered_map<char, int> manaDevotionMap;
|
||||
QVBoxLayout *layout;
|
||||
QHBoxLayout *barLayout;
|
||||
};
|
||||
|
||||
#endif // MANA_DEVOTION_WIDGET_H
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#include "deck_editor_card_info_dock_widget.h"
|
||||
|
||||
#include "../cards/card_info_frame_widget.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
DeckEditorCardInfoDockWidget::DeckEditorCardInfoDockWidget(AbstractTabDeckEditor *parent)
|
||||
: QDockWidget(parent), deckEditor(parent)
|
||||
{
|
||||
setObjectName("cardInfoDock");
|
||||
|
||||
setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
|
||||
|
||||
createCardInfoDock();
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DeckEditorCardInfoDockWidget::createCardInfoDock()
|
||||
{
|
||||
cardInfo = new CardInfoFrameWidget();
|
||||
cardInfo->setObjectName("cardInfo");
|
||||
auto *cardInfoFrame = new QVBoxLayout;
|
||||
cardInfoFrame->setObjectName("cardInfoFrame");
|
||||
cardInfoFrame->addWidget(cardInfo);
|
||||
|
||||
auto *cardInfoDockContents = new QWidget();
|
||||
cardInfoDockContents->setObjectName("cardInfoDockContents");
|
||||
cardInfoDockContents->setLayout(cardInfoFrame);
|
||||
setWidget(cardInfoDockContents);
|
||||
|
||||
installEventFilter(deckEditor);
|
||||
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
||||
}
|
||||
|
||||
void DeckEditorCardInfoDockWidget::updateCard(const ExactCard &_card)
|
||||
{
|
||||
cardInfo->setCard(_card);
|
||||
}
|
||||
|
||||
void DeckEditorCardInfoDockWidget::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Card Info"));
|
||||
cardInfo->retranslateUi();
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef DECK_EDITOR_CARD_INFO_DOCK_WIDGET_H
|
||||
#define DECK_EDITOR_CARD_INFO_DOCK_WIDGET_H
|
||||
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../cards/card_info_frame_widget.h"
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
class AbstractTabDeckEditor;
|
||||
class DeckEditorCardInfoDockWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorCardInfoDockWidget(AbstractTabDeckEditor *parent);
|
||||
void createCardInfoDock();
|
||||
void retranslateUi();
|
||||
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
CardInfoFrameWidget *cardInfo;
|
||||
|
||||
public slots:
|
||||
void updateCard(const ExactCard &_card);
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_CARD_INFO_DOCK_WIDGET_H
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
#include "deck_editor_database_display_widget.h"
|
||||
|
||||
#include "../../../database/card_database_manager.h"
|
||||
#include "../../../filters/syntax_help.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../../../tabs/tab_supervisor.h"
|
||||
#include "../../pixel_map_generator.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QFile>
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QTextBrowser>
|
||||
#include <QToolButton>
|
||||
#include <QTreeView>
|
||||
|
||||
static bool canBeCommander(const CardInfo &cardInfo)
|
||||
{
|
||||
return (cardInfo.getCardType().contains("Legendary", Qt::CaseInsensitive) &&
|
||||
cardInfo.getCardType().contains("Creature", Qt::CaseInsensitive)) ||
|
||||
cardInfo.getText().contains("can be your commander", Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent)
|
||||
: QWidget(parent), deckEditor(parent)
|
||||
{
|
||||
setObjectName("centralWidget");
|
||||
|
||||
centralFrame = new QVBoxLayout(this);
|
||||
centralFrame->setObjectName("centralFrame");
|
||||
setLayout(centralFrame);
|
||||
|
||||
searchEdit = new SearchLineEdit();
|
||||
searchEdit->setObjectName("searchEdit");
|
||||
searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)"));
|
||||
searchEdit->setClearButtonEnabled(true);
|
||||
searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
|
||||
auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
|
||||
searchEdit->installEventFilter(&searchKeySignals);
|
||||
|
||||
setFocusProxy(searchEdit);
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
searchKeySignals.setObjectName("searchKeySignals");
|
||||
connect(searchEdit, &SearchLineEdit::textChanged, this, &DeckEditorDatabaseDisplayWidget::updateSearch);
|
||||
connect(&searchKeySignals, &KeySignals::onEnter, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlAltEqual, this,
|
||||
&DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlAltRBracket, this,
|
||||
&DeckEditorDatabaseDisplayWidget::actAddCardToSideboard);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlAltMinus, this,
|
||||
&DeckEditorDatabaseDisplayWidget::actDecrementCardFromMainDeck);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlAltLBracket, this,
|
||||
&DeckEditorDatabaseDisplayWidget::actDecrementCardFromSideboard);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlAltEnter, this,
|
||||
&DeckEditorDatabaseDisplayWidget::actAddCardToSideboard);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlEnter, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlC, this, &DeckEditorDatabaseDisplayWidget::copyDatabaseCellContents);
|
||||
connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); });
|
||||
|
||||
databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), true, this);
|
||||
databaseModel->setObjectName("databaseModel");
|
||||
databaseDisplayModel = new CardDatabaseDisplayModel(this);
|
||||
databaseDisplayModel->setObjectName("databaseDisplayModel");
|
||||
databaseDisplayModel->setSourceModel(databaseModel);
|
||||
databaseDisplayModel->setFilterKeyColumn(0);
|
||||
|
||||
databaseView = new QTreeView(this);
|
||||
databaseView->setObjectName("databaseView");
|
||||
databaseView->setFocusProxy(searchEdit);
|
||||
databaseView->setUniformRowHeights(true);
|
||||
databaseView->setRootIsDecorated(false);
|
||||
databaseView->setAlternatingRowColors(true);
|
||||
databaseView->setSortingEnabled(true);
|
||||
databaseView->sortByColumn(0, Qt::AscendingOrder);
|
||||
databaseView->setModel(databaseDisplayModel);
|
||||
databaseView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(databaseView, &QTreeView::customContextMenuRequested, this,
|
||||
&DeckEditorDatabaseDisplayWidget::databaseCustomMenu);
|
||||
connect(databaseView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
|
||||
&DeckEditorDatabaseDisplayWidget::updateCard);
|
||||
connect(databaseView, &QTreeView::doubleClicked, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck);
|
||||
|
||||
QByteArray dbHeaderState = SettingsCache::instance().layouts().getDeckEditorDbHeaderState();
|
||||
if (dbHeaderState.isNull()) {
|
||||
// first run
|
||||
databaseView->setColumnWidth(0, 200);
|
||||
} else {
|
||||
databaseView->header()->restoreState(dbHeaderState);
|
||||
}
|
||||
connect(databaseView->header(), &QHeaderView::geometriesChanged, this,
|
||||
&DeckEditorDatabaseDisplayWidget::saveDbHeaderState);
|
||||
|
||||
searchEdit->setTreeView(databaseView);
|
||||
|
||||
aAddCard = new QAction(QString(), this);
|
||||
aAddCard->setIcon(QPixmap("theme:icons/arrow_right_green"));
|
||||
connect(aAddCard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck);
|
||||
auto *tbAddCard = new QToolButton(this);
|
||||
tbAddCard->setDefaultAction(aAddCard);
|
||||
|
||||
aAddCardToSideboard = new QAction(QString(), this);
|
||||
aAddCardToSideboard->setIcon(QPixmap("theme:icons/arrow_right_blue"));
|
||||
connect(aAddCardToSideboard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard);
|
||||
auto *tbAddCardToSideboard = new QToolButton(this);
|
||||
tbAddCardToSideboard->setDefaultAction(aAddCardToSideboard);
|
||||
|
||||
searchLayout = new QHBoxLayout;
|
||||
searchLayout->setObjectName("searchLayout");
|
||||
searchLayout->addWidget(searchEdit);
|
||||
searchLayout->addWidget(tbAddCard);
|
||||
searchLayout->addWidget(tbAddCardToSideboard);
|
||||
|
||||
centralFrame->addLayout(searchLayout);
|
||||
centralFrame->addWidget(databaseView);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::updateSearch(const QString &search)
|
||||
{
|
||||
databaseDisplayModel->setStringFilter(search);
|
||||
QModelIndexList sel = databaseView->selectionModel()->selectedRows();
|
||||
if (sel.isEmpty() && databaseDisplayModel->rowCount())
|
||||
databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0),
|
||||
QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters()
|
||||
{
|
||||
databaseDisplayModel->clearFilterAll();
|
||||
searchEdit->setText("");
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/)
|
||||
{
|
||||
const QString cardName = current.sibling(current.row(), 0).data().toString();
|
||||
|
||||
if (!current.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
|
||||
emit cardChanged(getCardOrPinnedPrinting(cardName));
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck()
|
||||
{
|
||||
emit addCardToMainDeck(currentCard());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::actAddCardToSideboard()
|
||||
{
|
||||
emit addCardToSideboard(currentCard());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::actDecrementCardFromMainDeck()
|
||||
{
|
||||
emit decrementCardFromMainDeck(currentCard());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::actDecrementCardFromSideboard()
|
||||
{
|
||||
emit decrementCardFromSideboard(currentCard());
|
||||
}
|
||||
|
||||
ExactCard DeckEditorDatabaseDisplayWidget::currentCard() const
|
||||
{
|
||||
const QModelIndex currentIndex = databaseView->selectionModel()->currentIndex();
|
||||
if (!currentIndex.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
|
||||
|
||||
return getCardOrPinnedPrinting(cardName);
|
||||
}
|
||||
|
||||
ExactCard DeckEditorDatabaseDisplayWidget::getCardOrPinnedPrinting(QString cardName) const
|
||||
{
|
||||
const auto &cardProviderId = SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardName);
|
||||
|
||||
ExactCard card = CardDatabaseManager::getInstance()->getCard({cardName});
|
||||
|
||||
if (cardProviderId != "") {
|
||||
return ExactCard(card.getCardPtr(),
|
||||
CardDatabaseManager::getInstance()->getSpecificPrinting({cardName, cardProviderId}));
|
||||
}
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::databaseCustomMenu(QPoint point)
|
||||
{
|
||||
QMenu menu;
|
||||
ExactCard card = currentCard();
|
||||
|
||||
if (card) {
|
||||
// add to deck and sideboard options
|
||||
QAction *addToDeck, *addToSideboard, *selectPrinting, *edhRecCommander, *edhRecCard;
|
||||
addToDeck = menu.addAction(tr("Add to Deck"));
|
||||
addToSideboard = menu.addAction(tr("Add to Sideboard"));
|
||||
selectPrinting = menu.addAction(tr("Select Printing"));
|
||||
if (canBeCommander(card.getInfo())) {
|
||||
edhRecCommander = menu.addAction(tr("Show on EDHRec (Commander)"));
|
||||
connect(edhRecCommander, &QAction::triggered, this,
|
||||
[this, card] { deckEditor->getTabSupervisor()->addEdhrecTab(card.getCardPtr(), true); });
|
||||
}
|
||||
edhRecCard = menu.addAction(tr("Show on EDHRec (Card)"));
|
||||
|
||||
connect(addToDeck, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck);
|
||||
connect(addToSideboard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard);
|
||||
connect(selectPrinting, &QAction::triggered, this, [this, card] { deckEditor->showPrintingSelector(); });
|
||||
connect(edhRecCard, &QAction::triggered, this,
|
||||
[this, card] { deckEditor->getTabSupervisor()->addEdhrecTab(card.getCardPtr()); });
|
||||
|
||||
// filling out the related cards submenu
|
||||
auto *relatedMenu = new QMenu(tr("Show Related cards"));
|
||||
menu.addMenu(relatedMenu);
|
||||
auto relatedCards = card.getInfo().getAllRelatedCards();
|
||||
if (relatedCards.isEmpty()) {
|
||||
relatedMenu->setDisabled(true);
|
||||
} else {
|
||||
for (const CardRelation *rel : relatedCards) {
|
||||
const QString &relatedCardName = rel->getName();
|
||||
QAction *relatedCard = relatedMenu->addAction(relatedCardName);
|
||||
connect(
|
||||
relatedCard, &QAction::triggered, deckEditor->cardInfoDockWidget->cardInfo,
|
||||
[this, relatedCardName] { deckEditor->cardInfoDockWidget->cardInfo->setCard(relatedCardName); });
|
||||
}
|
||||
}
|
||||
menu.exec(databaseView->mapToGlobal(point));
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::copyDatabaseCellContents()
|
||||
{
|
||||
auto _data = databaseView->selectionModel()->currentIndex().data();
|
||||
QApplication::clipboard()->setText(_data.toString());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::saveDbHeaderState()
|
||||
{
|
||||
SettingsCache::instance().layouts().setDeckEditorDbHeaderState(databaseView->header()->saveState());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::setFilterTree(FilterTree *filterTree)
|
||||
{
|
||||
databaseDisplayModel->setFilterTree(filterTree);
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::retranslateUi()
|
||||
{
|
||||
aAddCard->setText(tr("Add card to &maindeck"));
|
||||
aAddCardToSideboard->setText(tr("Add card to &sideboard"));
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#ifndef DECK_EDITOR_DATABASE_DISPLAY_WIDGET_H
|
||||
#define DECK_EDITOR_DATABASE_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../database/card_database_model.h"
|
||||
#include "../../../deck/custom_line_edit.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../../../utility/key_signals.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class AbstractTabDeckEditor;
|
||||
class DeckEditorDatabaseDisplayWidget : public QWidget
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent);
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
SearchLineEdit *searchEdit;
|
||||
CardDatabaseModel *databaseModel;
|
||||
CardDatabaseDisplayModel *databaseDisplayModel;
|
||||
|
||||
public slots:
|
||||
ExactCard currentCard() const;
|
||||
ExactCard getCardOrPinnedPrinting(QString cardName) const;
|
||||
void setFilterTree(FilterTree *filterTree);
|
||||
void clearAllDatabaseFilters();
|
||||
|
||||
signals:
|
||||
void addCardToMainDeck(const ExactCard &card);
|
||||
void addCardToSideboard(const ExactCard &card);
|
||||
void decrementCardFromMainDeck(const ExactCard &card);
|
||||
void decrementCardFromSideboard(const ExactCard &card);
|
||||
void cardChanged(const ExactCard &_card);
|
||||
|
||||
private:
|
||||
KeySignals searchKeySignals;
|
||||
QTreeView *databaseView;
|
||||
QHBoxLayout *searchLayout;
|
||||
QAction *aAddCard, *aAddCardToSideboard;
|
||||
QVBoxLayout *centralFrame;
|
||||
QWidget *centralWidget;
|
||||
|
||||
private slots:
|
||||
void retranslateUi();
|
||||
void updateSearch(const QString &search);
|
||||
void updateCard(const QModelIndex ¤t, const QModelIndex &);
|
||||
void actAddCardToMainDeck();
|
||||
void actAddCardToSideboard();
|
||||
void actDecrementCardFromMainDeck();
|
||||
void actDecrementCardFromSideboard();
|
||||
void databaseCustomMenu(QPoint point);
|
||||
void copyDatabaseCellContents();
|
||||
void saveDbHeaderState();
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_DATABASE_DISPLAY_WIDGET_H
|
||||
|
|
@ -0,0 +1,613 @@
|
|||
#include "deck_editor_deck_dock_widget.h"
|
||||
|
||||
#include "../../../database/card_database_manager.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDockWidget>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QSplitter>
|
||||
#include <QTextEdit>
|
||||
#include <trice_limits.h>
|
||||
|
||||
DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent)
|
||||
: QDockWidget(parent), deckEditor(parent)
|
||||
{
|
||||
setObjectName("deckDock");
|
||||
|
||||
setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
|
||||
|
||||
installEventFilter(deckEditor);
|
||||
connect(this, &DeckEditorDeckDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
||||
createDeckDock();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::createDeckDock()
|
||||
{
|
||||
deckModel = new DeckListModel(this);
|
||||
deckModel->setObjectName("deckModel");
|
||||
connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash);
|
||||
deckView = new QTreeView();
|
||||
deckView->setObjectName("deckView");
|
||||
deckView->setModel(deckModel);
|
||||
deckView->setUniformRowHeights(true);
|
||||
deckView->setSortingEnabled(true);
|
||||
deckView->sortByColumn(1, Qt::AscendingOrder);
|
||||
deckView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
deckView->installEventFilter(&deckViewKeySignals);
|
||||
deckView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
connect(deckView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
|
||||
&DeckEditorDeckDockWidget::updateCard);
|
||||
connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapCard);
|
||||
connect(deckView, &QTreeView::customContextMenuRequested, this, &DeckEditorDeckDockWidget::decklistCustomMenu);
|
||||
connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapCard);
|
||||
connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrement);
|
||||
connect(&deckViewKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDeckDockWidget::actIncrement);
|
||||
connect(&deckViewKeySignals, &KeySignals::onCtrlAltMinus, this, &DeckEditorDeckDockWidget::actDecrementSelection);
|
||||
connect(&deckViewKeySignals, &KeySignals::onShiftRight, this, &DeckEditorDeckDockWidget::actIncrement);
|
||||
connect(&deckViewKeySignals, &KeySignals::onShiftLeft, this, &DeckEditorDeckDockWidget::actDecrementSelection);
|
||||
connect(&deckViewKeySignals, &KeySignals::onDelete, this, &DeckEditorDeckDockWidget::actRemoveCard);
|
||||
|
||||
nameLabel = new QLabel();
|
||||
nameLabel->setObjectName("nameLabel");
|
||||
nameEdit = new LineEditUnfocusable;
|
||||
nameEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
nameEdit->setObjectName("nameEdit");
|
||||
nameLabel->setBuddy(nameEdit);
|
||||
connect(nameEdit, &LineEditUnfocusable::textChanged, this, &DeckEditorDeckDockWidget::updateName);
|
||||
|
||||
quickSettingsWidget = new SettingsButtonWidget(this);
|
||||
|
||||
showBannerCardCheckBox = new QCheckBox();
|
||||
showBannerCardCheckBox->setObjectName("showBannerCardCheckBox");
|
||||
showBannerCardCheckBox->setChecked(SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
|
||||
connect(showBannerCardCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
|
||||
&SettingsCache::setDeckEditorBannerCardComboBoxVisible);
|
||||
connect(&SettingsCache::instance(), &SettingsCache::deckEditorBannerCardComboBoxVisibleChanged, this,
|
||||
&DeckEditorDeckDockWidget::updateShowBannerCardComboBox);
|
||||
|
||||
showTagsWidgetCheckBox = new QCheckBox();
|
||||
showTagsWidgetCheckBox->setObjectName("showTagsWidgetCheckBox");
|
||||
showTagsWidgetCheckBox->setChecked(SettingsCache::instance().getDeckEditorTagsWidgetVisible());
|
||||
connect(showTagsWidgetCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
|
||||
&SettingsCache::setDeckEditorTagsWidgetVisible);
|
||||
connect(&SettingsCache::instance(), &SettingsCache::deckEditorTagsWidgetVisibleChanged, this,
|
||||
&DeckEditorDeckDockWidget::updateShowTagsWidget);
|
||||
|
||||
quickSettingsWidget->addSettingsWidget(showBannerCardCheckBox);
|
||||
quickSettingsWidget->addSettingsWidget(showTagsWidgetCheckBox);
|
||||
|
||||
commentsLabel = new QLabel();
|
||||
commentsLabel->setObjectName("commentsLabel");
|
||||
commentsEdit = new QTextEdit;
|
||||
commentsEdit->setAcceptRichText(false);
|
||||
commentsEdit->setMinimumHeight(nameEdit->minimumSizeHint().height());
|
||||
commentsEdit->setObjectName("commentsEdit");
|
||||
commentsLabel->setBuddy(commentsEdit);
|
||||
connect(commentsEdit, &QTextEdit::textChanged, this, &DeckEditorDeckDockWidget::updateComments);
|
||||
bannerCardLabel = new QLabel();
|
||||
bannerCardLabel->setObjectName("bannerCardLabel");
|
||||
bannerCardLabel->setText(tr("Banner Card"));
|
||||
bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
|
||||
bannerCardComboBox = new QComboBox(this);
|
||||
connect(deckModel, &DeckListModel::dataChanged, this, [this]() {
|
||||
// Delay the update to avoid race conditions
|
||||
QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox);
|
||||
});
|
||||
connect(bannerCardComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&DeckEditorDeckDockWidget::setBannerCard);
|
||||
bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
|
||||
|
||||
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList());
|
||||
deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible());
|
||||
|
||||
activeGroupCriteriaLabel = new QLabel(this);
|
||||
|
||||
activeGroupCriteriaComboBox = new QComboBox(this);
|
||||
activeGroupCriteriaComboBox->addItem(tr("Main Type"), DeckListModelGroupCriteria::MAIN_TYPE);
|
||||
activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST);
|
||||
activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR);
|
||||
connect(activeGroupCriteriaComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() {
|
||||
deckModel->setActiveGroupCriteria(
|
||||
static_cast<DeckListModelGroupCriteria>(activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt()));
|
||||
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
||||
deckView->expandAll();
|
||||
deckView->expandAll();
|
||||
});
|
||||
|
||||
aIncrement = new QAction(QString(), this);
|
||||
aIncrement->setIcon(QPixmap("theme:icons/increment"));
|
||||
connect(aIncrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actIncrement);
|
||||
auto *tbIncrement = new QToolButton(this);
|
||||
tbIncrement->setDefaultAction(aIncrement);
|
||||
|
||||
aDecrement = new QAction(QString(), this);
|
||||
aDecrement->setIcon(QPixmap("theme:icons/decrement"));
|
||||
connect(aDecrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actDecrementSelection);
|
||||
auto *tbDecrement = new QToolButton(this);
|
||||
tbDecrement->setDefaultAction(aDecrement);
|
||||
|
||||
aRemoveCard = new QAction(QString(), this);
|
||||
aRemoveCard->setIcon(QPixmap("theme:icons/remove_row"));
|
||||
connect(aRemoveCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actRemoveCard);
|
||||
auto *tbRemoveCard = new QToolButton(this);
|
||||
tbRemoveCard->setDefaultAction(aRemoveCard);
|
||||
|
||||
aSwapCard = new QAction(QString(), this);
|
||||
aSwapCard->setIcon(QPixmap("theme:icons/swap"));
|
||||
connect(aSwapCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actSwapCard);
|
||||
auto *tbSwapCard = new QToolButton(this);
|
||||
tbSwapCard->setDefaultAction(aSwapCard);
|
||||
|
||||
auto *upperLayout = new QGridLayout;
|
||||
upperLayout->setObjectName("upperLayout");
|
||||
upperLayout->setContentsMargins(11, 11, 11, 0);
|
||||
|
||||
upperLayout->addWidget(nameLabel, 0, 0);
|
||||
upperLayout->addWidget(nameEdit, 0, 1);
|
||||
upperLayout->addWidget(quickSettingsWidget, 0, 2);
|
||||
|
||||
upperLayout->addWidget(commentsLabel, 1, 0);
|
||||
upperLayout->addWidget(commentsEdit, 1, 1);
|
||||
|
||||
upperLayout->addWidget(bannerCardLabel, 2, 0);
|
||||
upperLayout->addWidget(bannerCardComboBox, 2, 1);
|
||||
|
||||
upperLayout->addWidget(deckTagsDisplayWidget, 3, 1);
|
||||
|
||||
upperLayout->addWidget(activeGroupCriteriaLabel, 4, 0);
|
||||
upperLayout->addWidget(activeGroupCriteriaComboBox, 4, 1);
|
||||
|
||||
hashLabel1 = new QLabel();
|
||||
hashLabel1->setObjectName("hashLabel1");
|
||||
auto *hashSizePolicy = new QSizePolicy();
|
||||
hashSizePolicy->setHorizontalPolicy(QSizePolicy::Fixed);
|
||||
hashLabel1->setSizePolicy(*hashSizePolicy);
|
||||
hashLabel = new LineEditUnfocusable;
|
||||
hashLabel->setObjectName("hashLabel");
|
||||
hashLabel->setReadOnly(true);
|
||||
hashLabel->setFrame(false);
|
||||
|
||||
auto *lowerLayout = new QGridLayout;
|
||||
lowerLayout->setObjectName("lowerLayout");
|
||||
lowerLayout->addWidget(hashLabel1, 0, 0);
|
||||
lowerLayout->addWidget(hashLabel, 0, 1);
|
||||
lowerLayout->addWidget(tbIncrement, 0, 2);
|
||||
lowerLayout->addWidget(tbDecrement, 0, 3);
|
||||
lowerLayout->addWidget(tbRemoveCard, 0, 4);
|
||||
lowerLayout->addWidget(tbSwapCard, 0, 5);
|
||||
lowerLayout->addWidget(deckView, 1, 0, 1, 6);
|
||||
|
||||
// Create widgets for both layouts to make splitter work correctly
|
||||
auto *topWidget = new QWidget;
|
||||
topWidget->setLayout(upperLayout);
|
||||
auto *bottomWidget = new QWidget;
|
||||
bottomWidget->setLayout(lowerLayout);
|
||||
|
||||
auto *split = new QSplitter;
|
||||
split->setObjectName("deckSplitter");
|
||||
split->setOrientation(Qt::Vertical);
|
||||
split->setChildrenCollapsible(true);
|
||||
split->addWidget(topWidget);
|
||||
split->addWidget(bottomWidget);
|
||||
split->setStretchFactor(0, 1);
|
||||
split->setStretchFactor(1, 4);
|
||||
|
||||
auto *rightFrame = new QVBoxLayout;
|
||||
rightFrame->setObjectName("rightFrame");
|
||||
rightFrame->addWidget(split);
|
||||
|
||||
auto *deckDockContents = new QWidget();
|
||||
deckDockContents->setObjectName("deckDockContents");
|
||||
deckDockContents->setLayout(rightFrame);
|
||||
setWidget(deckDockContents);
|
||||
|
||||
refreshShortcuts();
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
ExactCard DeckEditorDeckDockWidget::getCurrentCard()
|
||||
{
|
||||
QModelIndex current = deckView->selectionModel()->currentIndex();
|
||||
if (!current.isValid())
|
||||
return {};
|
||||
const QString cardName = current.sibling(current.row(), 1).data().toString();
|
||||
const QString cardProviderID = current.sibling(current.row(), 4).data().toString();
|
||||
const QModelIndex gparent = current.parent().parent();
|
||||
|
||||
if (!gparent.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString();
|
||||
|
||||
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
|
||||
QString cardName = current.sibling(current.row(), 1).data().toString();
|
||||
QString providerId = current.sibling(current.row(), 4).data().toString();
|
||||
if (ExactCard selectedCard = CardDatabaseManager::getInstance()->getCard({cardName, providerId})) {
|
||||
return selectedCard;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const QModelIndex & /*previous*/)
|
||||
{
|
||||
if (ExactCard card = getCurrentCard()) {
|
||||
emit cardChanged(card);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateName(const QString &name)
|
||||
{
|
||||
deckModel->getDeckList()->setName(name);
|
||||
deckEditor->setModified(name.isEmpty());
|
||||
emit nameChanged();
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateComments()
|
||||
{
|
||||
deckModel->getDeckList()->setComments(commentsEdit->toPlainText());
|
||||
deckEditor->setModified(commentsEdit->toPlainText().isEmpty());
|
||||
emit commentsChanged();
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateHash()
|
||||
{
|
||||
hashLabel->setText(deckModel->getDeckList()->getDeckHash());
|
||||
emit hashChanged();
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||
{
|
||||
// Store the current text of the combo box
|
||||
QString currentText = bannerCardComboBox->currentText();
|
||||
|
||||
// Block signals temporarily
|
||||
bool wasBlocked = bannerCardComboBox->blockSignals(true);
|
||||
|
||||
// Clear the existing items in the combo box
|
||||
bannerCardComboBox->clear();
|
||||
|
||||
// Prepare the new items with deduplication
|
||||
QSet<QPair<QString, QString>> bannerCardSet;
|
||||
InnerDecklistNode *listRoot = deckModel->getDeckList()->getRoot();
|
||||
for (int i = 0; i < listRoot->size(); i++) {
|
||||
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
|
||||
for (int j = 0; j < currentZone->size(); j++) {
|
||||
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
|
||||
if (!currentCard)
|
||||
continue;
|
||||
|
||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||
if (CardDatabaseManager::getInstance()->getCard(currentCard->toCardRef())) {
|
||||
bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QList<QPair<QString, QString>> pairList = bannerCardSet.values();
|
||||
|
||||
// Sort QList by the first() element of the QPair
|
||||
std::sort(pairList.begin(), pairList.end(), [](const QPair<QString, QString> &a, const QPair<QString, QString> &b) {
|
||||
return a.first.toLower() < b.first.toLower();
|
||||
});
|
||||
|
||||
for (const auto &pair : pairList) {
|
||||
bannerCardComboBox->addItem(pair.first, QVariant::fromValue(pair));
|
||||
}
|
||||
|
||||
// Try to restore the previous selection by finding the currentText
|
||||
int restoredIndex = bannerCardComboBox->findText(currentText);
|
||||
if (restoredIndex != -1) {
|
||||
bannerCardComboBox->setCurrentIndex(restoredIndex);
|
||||
if (deckModel->getDeckList()->getBannerCard().providerId !=
|
||||
bannerCardComboBox->currentData().value<QPair<QString, QString>>().second) {
|
||||
setBannerCard(restoredIndex);
|
||||
}
|
||||
} else {
|
||||
// Add a placeholder "-" and set it as the current selection
|
||||
int bannerIndex = bannerCardComboBox->findText(deckModel->getDeckList()->getBannerCard().name);
|
||||
if (bannerIndex != -1) {
|
||||
bannerCardComboBox->setCurrentIndex(bannerIndex);
|
||||
} else {
|
||||
bannerCardComboBox->insertItem(0, "-");
|
||||
bannerCardComboBox->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the previous signal blocking state
|
||||
bannerCardComboBox->blockSignals(wasBlocked);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */)
|
||||
{
|
||||
auto [name, id] = bannerCardComboBox->currentData().value<QPair<QString, QString>>();
|
||||
deckModel->getDeckList()->setBannerCard({name, id});
|
||||
deckEditor->setModified(true);
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible)
|
||||
{
|
||||
bannerCardLabel->setHidden(!visible);
|
||||
bannerCardComboBox->setHidden(!visible);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateShowTagsWidget(const bool visible)
|
||||
{
|
||||
deckTagsDisplayWidget->setHidden(!visible);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck()
|
||||
{
|
||||
if (deckModel->getDeckList()->getBannerCard().name == "") {
|
||||
if (bannerCardComboBox->findText("-") != -1) {
|
||||
bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText("-"));
|
||||
} else {
|
||||
bannerCardComboBox->insertItem(0, "-");
|
||||
bannerCardComboBox->setCurrentIndex(0);
|
||||
}
|
||||
} else {
|
||||
bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard().name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the currently active deck for this tab
|
||||
* @param _deck The deck. Takes ownership of the object
|
||||
*/
|
||||
void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck)
|
||||
{
|
||||
deckModel->setDeckList(_deck);
|
||||
|
||||
nameEdit->setText(deckModel->getDeckList()->getName());
|
||||
commentsEdit->setText(deckModel->getDeckList()->getComments());
|
||||
|
||||
syncBannerCardComboBoxSelectionWithDeck();
|
||||
updateBannerCardComboBox();
|
||||
updateHash();
|
||||
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
||||
deckView->expandAll();
|
||||
deckView->expandAll();
|
||||
|
||||
deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
|
||||
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
DeckLoader *DeckEditorDeckDockWidget::getDeckList()
|
||||
{
|
||||
return deckModel->getDeckList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the tab to the state for a blank new tab.
|
||||
*/
|
||||
void DeckEditorDeckDockWidget::cleanDeck()
|
||||
{
|
||||
deckModel->cleanList();
|
||||
nameEdit->setText(QString());
|
||||
emit nameChanged();
|
||||
commentsEdit->setText(QString());
|
||||
emit commentsChanged();
|
||||
hashLabel->setText(QString());
|
||||
emit hashChanged();
|
||||
emit deckModified();
|
||||
emit deckChanged();
|
||||
updateBannerCardComboBox();
|
||||
deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index)
|
||||
{
|
||||
if (index.parent().isValid())
|
||||
recursiveExpand(index.parent());
|
||||
deckView->expand(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index of all the currently selected card nodes in the decklist table.
|
||||
* The list is in reverse order of the visual selection, so that rows can be deleted while iterating over them.
|
||||
*
|
||||
* @return A model index list containing all selected card nodes
|
||||
*/
|
||||
QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const
|
||||
{
|
||||
auto selectedRows = deckView->selectionModel()->selectedRows();
|
||||
|
||||
const auto notLeafNode = [this](const auto &index) { return deckModel->hasChildren(index); };
|
||||
selectedRows.erase(std::remove_if(selectedRows.begin(), selectedRows.end(), notLeafNode), selectedRows.end());
|
||||
|
||||
std::reverse(selectedRows.begin(), selectedRows.end());
|
||||
return selectedRows;
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actIncrement()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
for (const auto &index : selectedRows) {
|
||||
offsetCountAtIndex(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actSwapCard()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
// 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
|
||||
if (selectedRows.length() == 1) {
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
||||
bool isModified = false;
|
||||
for (const auto ¤tIndex : selectedRows) {
|
||||
if (swapCard(currentIndex)) {
|
||||
isModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
|
||||
if (isModified) {
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the card at the index between the maindeck and sideboard
|
||||
*
|
||||
* @param currentIndex The index to swap.
|
||||
* @return True if the swap was successful
|
||||
*/
|
||||
bool DeckEditorDeckDockWidget::swapCard(const QModelIndex ¤tIndex)
|
||||
{
|
||||
if (!currentIndex.isValid())
|
||||
return false;
|
||||
const QString cardName = currentIndex.sibling(currentIndex.row(), 1).data().toString();
|
||||
const QString cardProviderID = currentIndex.sibling(currentIndex.row(), 4).data().toString();
|
||||
const QModelIndex gparent = currentIndex.parent().parent();
|
||||
|
||||
if (!gparent.isValid())
|
||||
return false;
|
||||
|
||||
const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString();
|
||||
offsetCountAtIndex(currentIndex, -1);
|
||||
const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
|
||||
|
||||
ExactCard card = CardDatabaseManager::getInstance()->getCard({cardName, cardProviderID});
|
||||
QModelIndex newCardIndex = card ? deckModel->addCard(card, otherZoneName)
|
||||
// Third argument (true) says create the card no matter what, even if not in DB
|
||||
: deckModel->addPreferredPrintingCard(cardName, otherZoneName, true);
|
||||
recursiveExpand(newCardIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString zoneName)
|
||||
{
|
||||
if (!card)
|
||||
return;
|
||||
if (card.getInfo().getIsToken())
|
||||
zoneName = DECK_ZONE_TOKENS;
|
||||
|
||||
QString providerId = card.getPrinting().getUuid();
|
||||
QString collectorNumber = card.getPrinting().getProperty("num");
|
||||
|
||||
QModelIndex idx = deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber);
|
||||
if (!idx.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
deckView->clearSelection();
|
||||
deckView->setCurrentIndex(idx);
|
||||
offsetCountAtIndex(idx, -1);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actDecrementSelection()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
// 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
|
||||
if (selectedRows.length() == 1) {
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
||||
for (const auto &index : selectedRows) {
|
||||
offsetCountAtIndex(index, -1);
|
||||
}
|
||||
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actRemoveCard()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
// 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
|
||||
if (selectedRows.length() == 1) {
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
||||
bool isModified = false;
|
||||
for (const auto &index : selectedRows) {
|
||||
if (!index.isValid() || deckModel->hasChildren(index)) {
|
||||
continue;
|
||||
}
|
||||
deckModel->removeRow(index.row(), index.parent());
|
||||
isModified = true;
|
||||
}
|
||||
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
|
||||
if (isModified) {
|
||||
emit deckModified();
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int offset)
|
||||
{
|
||||
if (!idx.isValid() || deckModel->hasChildren(idx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QModelIndex numberIndex = idx.sibling(idx.row(), 0);
|
||||
const int count = deckModel->data(numberIndex, Qt::EditRole).toInt();
|
||||
const int new_count = count + offset;
|
||||
if (new_count <= 0)
|
||||
deckModel->removeRow(idx.row(), idx.parent());
|
||||
else
|
||||
deckModel->setData(numberIndex, new_count, Qt::EditRole);
|
||||
|
||||
emit deckModified();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point)
|
||||
{
|
||||
QMenu menu;
|
||||
|
||||
QAction *selectPrinting = menu.addAction(tr("Select Printing"));
|
||||
|
||||
connect(selectPrinting, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::showPrintingSelector);
|
||||
|
||||
menu.exec(deckView->mapToGlobal(point));
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::refreshShortcuts()
|
||||
{
|
||||
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
||||
aRemoveCard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aRemoveCard"));
|
||||
aIncrement->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aIncrement"));
|
||||
aDecrement->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aDecrement"));
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Deck"));
|
||||
|
||||
nameLabel->setText(tr("Deck &name:"));
|
||||
quickSettingsWidget->setToolTip(tr("Banner Card/Tags Visibility Settings"));
|
||||
showBannerCardCheckBox->setText(tr("Show banner card selection menu"));
|
||||
showTagsWidgetCheckBox->setText(tr("Show tags selection menu"));
|
||||
commentsLabel->setText(tr("&Comments:"));
|
||||
activeGroupCriteriaLabel->setText(tr("Group by:"));
|
||||
hashLabel1->setText(tr("Hash:"));
|
||||
|
||||
aIncrement->setText(tr("&Increment number"));
|
||||
aDecrement->setText(tr("&Decrement number"));
|
||||
aRemoveCard->setText(tr("&Remove row"));
|
||||
aSwapCard->setText(tr("Swap card to/from sideboard"));
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
#ifndef DECK_EDITOR_DECK_DOCK_WIDGET_H
|
||||
#define DECK_EDITOR_DECK_DOCK_WIDGET_H
|
||||
|
||||
#include "../../../card/card_info.h"
|
||||
#include "../../../deck/custom_line_edit.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../../../utility/key_signals.h"
|
||||
#include "../visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDockWidget>
|
||||
#include <QLabel>
|
||||
#include <QTextEdit>
|
||||
#include <QTreeView>
|
||||
|
||||
class DeckListModel;
|
||||
class AbstractTabDeckEditor;
|
||||
class DeckEditorDeckDockWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent);
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QComboBox *bannerCardComboBox;
|
||||
void createDeckDock();
|
||||
ExactCard getCurrentCard();
|
||||
void retranslateUi();
|
||||
QString getDeckName()
|
||||
{
|
||||
return nameEdit->text();
|
||||
}
|
||||
QString getSimpleDeckName()
|
||||
{
|
||||
return nameEdit->text().simplified();
|
||||
}
|
||||
|
||||
public slots:
|
||||
void cleanDeck();
|
||||
void updateBannerCardComboBox();
|
||||
void setDeck(DeckLoader *_deck);
|
||||
DeckLoader *getDeckList();
|
||||
void actIncrement();
|
||||
bool swapCard(const QModelIndex &idx);
|
||||
void actDecrementCard(const ExactCard &card, QString zoneName);
|
||||
void actDecrementSelection();
|
||||
void actSwapCard();
|
||||
void actRemoveCard();
|
||||
void offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||
|
||||
signals:
|
||||
void nameChanged();
|
||||
void commentsChanged();
|
||||
void hashChanged();
|
||||
void deckChanged();
|
||||
void deckModified();
|
||||
void cardChanged(const ExactCard &_card);
|
||||
|
||||
private:
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
KeySignals deckViewKeySignals;
|
||||
QLabel *nameLabel;
|
||||
LineEditUnfocusable *nameEdit;
|
||||
SettingsButtonWidget *quickSettingsWidget;
|
||||
QCheckBox *showBannerCardCheckBox;
|
||||
QCheckBox *showTagsWidgetCheckBox;
|
||||
QLabel *commentsLabel;
|
||||
QTextEdit *commentsEdit;
|
||||
QLabel *bannerCardLabel;
|
||||
DeckPreviewDeckTagsDisplayWidget *deckTagsDisplayWidget;
|
||||
QLabel *hashLabel1;
|
||||
LineEditUnfocusable *hashLabel;
|
||||
QLabel *activeGroupCriteriaLabel;
|
||||
QComboBox *activeGroupCriteriaComboBox;
|
||||
|
||||
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;
|
||||
|
||||
void recursiveExpand(const QModelIndex &index);
|
||||
QModelIndexList getSelectedCardNodes() const;
|
||||
|
||||
private slots:
|
||||
void decklistCustomMenu(QPoint point);
|
||||
void updateCard(QModelIndex, const QModelIndex ¤t);
|
||||
void updateName(const QString &name);
|
||||
void updateComments();
|
||||
void setBannerCard(int);
|
||||
void updateHash();
|
||||
void refreshShortcuts();
|
||||
void updateShowBannerCardComboBox(bool visible);
|
||||
void updateShowTagsWidget(bool visible);
|
||||
void syncBannerCardComboBoxSelectionWithDeck();
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_DECK_DOCK_WIDGET_H
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
#include "deck_editor_filter_dock_widget.h"
|
||||
|
||||
#include "../../../database/card_database_model.h"
|
||||
#include "../../../filters/filter_builder.h"
|
||||
#include "../../../filters/filter_tree_model.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QMenu>
|
||||
#include <QToolButton>
|
||||
|
||||
DeckEditorFilterDockWidget::DeckEditorFilterDockWidget(AbstractTabDeckEditor *parent)
|
||||
: QDockWidget(parent), deckEditor(parent)
|
||||
{
|
||||
setObjectName("filterDock");
|
||||
|
||||
setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
|
||||
|
||||
createFiltersDock();
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::createFiltersDock()
|
||||
{
|
||||
filterModel = new FilterTreeModel();
|
||||
filterModel->setObjectName("filterModel");
|
||||
deckEditor->filterTreeChanged(filterModel->filterTree());
|
||||
filterView = new QTreeView;
|
||||
filterView->setObjectName("filterView");
|
||||
filterView->setModel(filterModel);
|
||||
filterView->setUniformRowHeights(true);
|
||||
filterView->setHeaderHidden(true);
|
||||
filterView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
filterView->installEventFilter(&filterViewKeySignals);
|
||||
connect(filterModel, &FilterTreeModel::layoutChanged, filterView, &QTreeView::expandAll);
|
||||
connect(filterView, &QTreeView::customContextMenuRequested, this,
|
||||
&DeckEditorFilterDockWidget::filterViewCustomContextMenu);
|
||||
connect(&filterViewKeySignals, &KeySignals::onDelete, this, &DeckEditorFilterDockWidget::actClearFilterOne);
|
||||
|
||||
auto *filterBuilder = new FilterBuilder;
|
||||
filterBuilder->setObjectName("filterBuilder");
|
||||
connect(filterBuilder, &FilterBuilder::add, filterModel, &FilterTreeModel::addFilter);
|
||||
|
||||
aClearFilterOne = new QAction(QString(), this);
|
||||
aClearFilterOne->setIcon(QPixmap("theme:icons/decrement"));
|
||||
connect(aClearFilterOne, &QAction::triggered, this, &DeckEditorFilterDockWidget::actClearFilterOne);
|
||||
|
||||
aClearFilterAll = new QAction(QString(), this);
|
||||
aClearFilterAll->setIcon(QPixmap("theme:icons/clearsearch"));
|
||||
connect(aClearFilterAll, &QAction::triggered, this, &DeckEditorFilterDockWidget::actClearFilterAll);
|
||||
|
||||
auto *filterDelOne = new QToolButton();
|
||||
filterDelOne->setObjectName("filterDelOne");
|
||||
filterDelOne->setDefaultAction(aClearFilterOne);
|
||||
filterDelOne->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
|
||||
auto *filterDelAll = new QToolButton();
|
||||
filterDelAll->setObjectName("filterDelAll");
|
||||
filterDelAll->setDefaultAction(aClearFilterAll);
|
||||
filterDelAll->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
|
||||
auto *filterLayout = new QGridLayout;
|
||||
filterLayout->setObjectName("filterLayout");
|
||||
filterLayout->setContentsMargins(0, 0, 0, 0);
|
||||
filterLayout->addWidget(filterBuilder, 0, 0, 1, 3);
|
||||
filterLayout->addWidget(filterView, 1, 0, 1, 3);
|
||||
filterLayout->addWidget(filterDelOne, 2, 0, 1, 1);
|
||||
filterLayout->addWidget(filterDelAll, 2, 2, 1, 1);
|
||||
|
||||
filterBox = new QWidget();
|
||||
filterBox->setObjectName("filterBox");
|
||||
filterBox->setLayout(filterLayout);
|
||||
|
||||
auto *filterFrame = new QVBoxLayout;
|
||||
filterFrame->setObjectName("filterFrame");
|
||||
filterFrame->addWidget(filterBox);
|
||||
|
||||
auto *filterDockContents = new QWidget(this);
|
||||
filterDockContents->setObjectName("filterDockContents");
|
||||
filterDockContents->setLayout(filterFrame);
|
||||
setWidget(filterDockContents);
|
||||
|
||||
installEventFilter(deckEditor);
|
||||
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::filterViewCustomContextMenu(const QPoint &point)
|
||||
{
|
||||
QMenu menu;
|
||||
QAction *action;
|
||||
QModelIndex idx;
|
||||
|
||||
idx = filterView->indexAt(point);
|
||||
if (!idx.isValid())
|
||||
return;
|
||||
|
||||
action = menu.addAction(QString("delete"));
|
||||
action->setData(point);
|
||||
connect(&menu, &QMenu::triggered, this, &DeckEditorFilterDockWidget::filterRemove);
|
||||
menu.exec(filterView->mapToGlobal(point));
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::filterRemove(const QAction *action)
|
||||
{
|
||||
QPoint point;
|
||||
QModelIndex idx;
|
||||
|
||||
point = action->data().toPoint();
|
||||
idx = filterView->indexAt(point);
|
||||
if (!idx.isValid())
|
||||
return;
|
||||
|
||||
filterModel->removeRow(idx.row(), idx.parent());
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::actClearFilterAll()
|
||||
{
|
||||
emit clearAllDatabaseFilters();
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::actClearFilterOne()
|
||||
{
|
||||
QModelIndexList selIndexes = filterView->selectionModel()->selectedIndexes();
|
||||
for (QModelIndex idx : selIndexes) {
|
||||
filterModel->removeRow(idx.row(), idx.parent());
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::refreshShortcuts()
|
||||
{
|
||||
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
||||
|
||||
aClearFilterAll->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterAll"));
|
||||
aClearFilterOne->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterOne"));
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Filters"));
|
||||
|
||||
aClearFilterAll->setText(tr("&Clear all filters"));
|
||||
aClearFilterOne->setText(tr("Delete selected"));
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef DECK_EDITOR_FILTER_DOCK_WIDGET_H
|
||||
#define DECK_EDITOR_FILTER_DOCK_WIDGET_H
|
||||
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../../../utility/key_signals.h"
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QTreeView>
|
||||
|
||||
class FilterTreeModel;
|
||||
class AbstractTabDeckEditor;
|
||||
class DeckEditorFilterDockWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorFilterDockWidget(AbstractTabDeckEditor *parent);
|
||||
void createFiltersDock();
|
||||
void retranslateUi();
|
||||
QAction *aClearFilterAll, *aClearFilterOne;
|
||||
|
||||
signals:
|
||||
void clearAllDatabaseFilters();
|
||||
|
||||
private:
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
FilterTreeModel *filterModel;
|
||||
QTreeView *filterView;
|
||||
KeySignals filterViewKeySignals;
|
||||
QWidget *filterBox;
|
||||
|
||||
private slots:
|
||||
void filterViewCustomContextMenu(const QPoint &point);
|
||||
void filterRemove(const QAction *action);
|
||||
void actClearFilterAll();
|
||||
void actClearFilterOne();
|
||||
void refreshShortcuts();
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_FILTER_DOCK_WIDGET_H
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#include "deck_editor_printing_selector_dock_widget.h"
|
||||
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
DeckEditorPrintingSelectorDockWidget::DeckEditorPrintingSelectorDockWidget(AbstractTabDeckEditor *parent)
|
||||
: QDockWidget(parent), deckEditor(parent)
|
||||
{
|
||||
setObjectName("printingSelectorDock");
|
||||
|
||||
setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
|
||||
setFloating(false);
|
||||
|
||||
createPrintingSelectorDock();
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DeckEditorPrintingSelectorDockWidget::createPrintingSelectorDock()
|
||||
{
|
||||
printingSelector = new PrintingSelector(this, deckEditor);
|
||||
printingSelector->setObjectName("printingSelector");
|
||||
auto *printingSelectorFrame = new QVBoxLayout;
|
||||
printingSelectorFrame->setObjectName("printingSelectorFrame");
|
||||
printingSelectorFrame->addWidget(printingSelector);
|
||||
|
||||
auto *printingSelectorDockContents = new QWidget();
|
||||
printingSelectorDockContents->setObjectName("printingSelectorDockContents");
|
||||
printingSelectorDockContents->setLayout(printingSelectorFrame);
|
||||
setWidget(printingSelectorDockContents);
|
||||
|
||||
installEventFilter(deckEditor);
|
||||
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
||||
}
|
||||
|
||||
void DeckEditorPrintingSelectorDockWidget::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Printing Selector"));
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef DECK_EDITOR_PRINTING_SELECTOR_DOCK_WIDGET_H
|
||||
#define DECK_EDITOR_PRINTING_SELECTOR_DOCK_WIDGET_H
|
||||
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../printing_selector/printing_selector.h"
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
class TabDeckEditor;
|
||||
class DeckEditorPrintingSelectorDockWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorPrintingSelectorDockWidget(AbstractTabDeckEditor *parent);
|
||||
void createPrintingSelectorDock();
|
||||
void retranslateUi();
|
||||
PrintingSelector *printingSelector;
|
||||
|
||||
private:
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_PRINTING_SELECTOR_DOCK_WIDGET_H
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
#include "background_sources.h"
|
||||
|
||||
// Required so moc generates Q_OBJECT macros
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#ifndef COCKATRICE_BACKGROUND_SOURCES_H
|
||||
#define COCKATRICE_BACKGROUND_SOURCES_H
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class BackgroundSources
|
||||
{
|
||||
Q_GADGET
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
Theme,
|
||||
RandomCardArt,
|
||||
DeckFileArt
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
|
||||
struct Entry
|
||||
{
|
||||
Type type;
|
||||
const char *id; // stable ID for settings
|
||||
const char *trKey; // key for translation
|
||||
};
|
||||
|
||||
static QList<Entry> all()
|
||||
{
|
||||
return {{Theme, "theme", QT_TR_NOOP("Theme")},
|
||||
{RandomCardArt, "random_card_art", QT_TR_NOOP("Art crop of random card")},
|
||||
{DeckFileArt, "deck_file_art", QT_TR_NOOP("Art crop of background.cod deck file")}};
|
||||
}
|
||||
|
||||
static QString toId(Type type)
|
||||
{
|
||||
for (const auto &e : all()) {
|
||||
if (e.type == type)
|
||||
return e.id;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static Type fromId(const QString &id)
|
||||
{
|
||||
for (const auto &e : all()) {
|
||||
if (id == e.id)
|
||||
return e.type;
|
||||
}
|
||||
return Theme; // default
|
||||
}
|
||||
|
||||
static QString toDisplay(Type type)
|
||||
{
|
||||
for (const auto &e : all()) {
|
||||
if (e.type == type)
|
||||
return QObject::tr(e.trKey);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_BACKGROUND_SOURCES_H
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
#include "banner_widget.h"
|
||||
|
||||
#include "../../../pixel_map_generator.h"
|
||||
|
||||
#include <QLinearGradient>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation orientation, int transparency)
|
||||
: QWidget(parent), gradientOrientation(orientation), transparency(qBound(0, transparency, 100))
|
||||
{
|
||||
auto layout = new QHBoxLayout(this);
|
||||
|
||||
iconLabel = new QLabel(this);
|
||||
iconLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
|
||||
// Create the banner label and set properties
|
||||
bannerLabel = new QLabel(text, this);
|
||||
bannerLabel->setAlignment(Qt::AlignCenter);
|
||||
bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold; color: white;");
|
||||
|
||||
layout->addWidget(iconLabel);
|
||||
layout->addWidget(bannerLabel);
|
||||
layout->addWidget(new QLabel(this)); // add dummy label to force text label to be centered
|
||||
setLayout(layout);
|
||||
|
||||
// Set minimum height for the widget
|
||||
setMinimumHeight(50);
|
||||
connect(this, &BannerWidget::buddyVisibilityChanged, this, &BannerWidget::toggleBuddyVisibility);
|
||||
|
||||
updateDropdownIconState();
|
||||
}
|
||||
|
||||
void BannerWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mousePressEvent(event);
|
||||
if (clickable) {
|
||||
emit buddyVisibilityChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void BannerWidget::setText(const QString &text) const
|
||||
{
|
||||
bannerLabel->setText(text);
|
||||
}
|
||||
|
||||
void BannerWidget::setClickable(bool _clickable)
|
||||
{
|
||||
clickable = _clickable;
|
||||
updateDropdownIconState();
|
||||
}
|
||||
|
||||
void BannerWidget::setBuddy(QWidget *_buddy)
|
||||
{
|
||||
buddy = _buddy;
|
||||
updateDropdownIconState();
|
||||
}
|
||||
|
||||
void BannerWidget::toggleBuddyVisibility() const
|
||||
{
|
||||
if (buddy) {
|
||||
buddy->setVisible(!buddy->isVisible());
|
||||
updateDropdownIconState();
|
||||
}
|
||||
}
|
||||
|
||||
void BannerWidget::updateDropdownIconState() const
|
||||
{
|
||||
if (clickable && buddy) {
|
||||
iconLabel->setPixmap(DropdownIconPixmapGenerator::generatePixmap(24, !buddy->isHidden()));
|
||||
} else {
|
||||
// we cannot directly hide the iconLabel, since it's needed to center the text; set an empty image instead
|
||||
iconLabel->setPixmap(QPixmap());
|
||||
}
|
||||
}
|
||||
|
||||
void BannerWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QPainter painter(this);
|
||||
|
||||
// Calculate alpha based on transparency percentage
|
||||
int alpha = (255 * transparency) / 100;
|
||||
|
||||
// Determine gradient direction
|
||||
QLinearGradient gradient;
|
||||
if (gradientOrientation == Qt::Vertical) {
|
||||
gradient = QLinearGradient(rect().topLeft(), rect().bottomLeft());
|
||||
} else {
|
||||
gradient = QLinearGradient(rect().topLeft(), rect().topRight());
|
||||
}
|
||||
|
||||
// Set neutral gradient colors with calculated transparency
|
||||
gradient.setColorAt(0, QColor(200, 200, 200, alpha)); // Light grey with alpha
|
||||
gradient.setColorAt(1, QColor(100, 100, 100, alpha / 1.5)); // Darker grey, slightly more transparent
|
||||
|
||||
// Fill the widget background with the gradient
|
||||
painter.fillRect(rect(), gradient);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef BANNER_WIDGET_H
|
||||
#define BANNER_WIDGET_H
|
||||
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class BannerWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BannerWidget(QWidget *parent,
|
||||
const QString &text,
|
||||
Qt::Orientation orientation = Qt::Vertical,
|
||||
int transparency = 80);
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void setText(const QString &text) const;
|
||||
void setClickable(bool _clickable);
|
||||
void setBuddy(QWidget *_buddy);
|
||||
QString getText() const
|
||||
{
|
||||
return bannerLabel->text();
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
QLabel *iconLabel;
|
||||
QLabel *bannerLabel;
|
||||
Qt::Orientation gradientOrientation;
|
||||
int transparency; // Transparency percentage for the gradient
|
||||
QWidget *buddy = nullptr;
|
||||
bool clickable = true;
|
||||
signals:
|
||||
void buddyVisibilityChanged();
|
||||
private slots:
|
||||
void toggleBuddyVisibility() const;
|
||||
void updateDropdownIconState() const;
|
||||
};
|
||||
|
||||
#endif // BANNER_WIDGET_H
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
#include "bar_widget.h"
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QPainter>
|
||||
|
||||
BarWidget::BarWidget(QString label, int value, int total, QColor barColor, QWidget *parent)
|
||||
: QWidget(parent), label(std::move(label)), value(value), total(total), barColor(barColor)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
QSize BarWidget::sizeHint() const
|
||||
{
|
||||
QFontMetrics metrics(font());
|
||||
int labelHeight = metrics.height();
|
||||
int valueHeight = metrics.height();
|
||||
|
||||
// Calculate the height dynamically based on the total
|
||||
int barHeight = (total > 0) ? (value * 200 / total) : 20; // Scale height proportionally
|
||||
int totalHeight = barHeight + labelHeight + valueHeight + 30; // Extra space for text
|
||||
return QSize(60, totalHeight); // Allow width to expand
|
||||
}
|
||||
|
||||
void BarWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
int widgetWidth = width();
|
||||
int widgetHeight = height();
|
||||
|
||||
// Calculate bar dimensions
|
||||
int barWidth = widgetWidth * 0.8; // Use 80% of the available width
|
||||
int fullBarHeight = widgetHeight - 40; // Leave space for labels
|
||||
int valueBarHeight = (total > 0) ? (value * fullBarHeight / total) : 0;
|
||||
|
||||
// Draw full bar background (gray)
|
||||
painter.setBrush(QColor(200, 200, 200));
|
||||
painter.drawRect((widgetWidth - barWidth) / 2, 10, barWidth, fullBarHeight);
|
||||
|
||||
// Draw the value-specific bar using the assigned color
|
||||
painter.setBrush(barColor);
|
||||
painter.drawRect((widgetWidth - barWidth) / 2, 10 + fullBarHeight - valueBarHeight, barWidth, valueBarHeight);
|
||||
|
||||
// Draw the CMC label
|
||||
painter.setPen(Qt::white);
|
||||
QRect textRect(0, widgetHeight - 30, widgetWidth, 20);
|
||||
painter.drawText(textRect, Qt::AlignCenter, label);
|
||||
|
||||
// Draw the value count
|
||||
painter.setPen(Qt::black);
|
||||
QRect valueRect(0, 10, widgetWidth, 20);
|
||||
painter.drawText(valueRect, Qt::AlignCenter, QString::number(value));
|
||||
|
||||
(void)event; // Suppress unused parameter warning
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef BAR_WIDGET_H
|
||||
#define BAR_WIDGET_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
|
||||
class BarWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BarWidget(QString label, int value, int total, QColor barColor = Qt::blue, QWidget *parent = nullptr);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
QString label;
|
||||
int value;
|
||||
int total;
|
||||
QColor barColor; // Store the bar color
|
||||
};
|
||||
|
||||
#endif // BAR_WIDGET_H
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
#include "dynamic_font_size_label.h"
|
||||
#define FONT_PRECISION (0.5)
|
||||
|
||||
#include <QDebug>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
DynamicFontSizeLabel::DynamicFontSizeLabel(QWidget *parent, Qt::WindowFlags f) : QLabel(parent, f)
|
||||
{
|
||||
setIndent(0);
|
||||
}
|
||||
|
||||
void DynamicFontSizeLabel::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
emit clicked();
|
||||
}
|
||||
|
||||
void DynamicFontSizeLabel::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
// QElapsedTimer timer;
|
||||
// timer.start();
|
||||
|
||||
QFont newFont = font();
|
||||
float fontSize = getWidgetMaximumFontSize(this, this->text());
|
||||
newFont.setPointSizeF(fontSize);
|
||||
setFont(newFont);
|
||||
// qDebug() << "Font size set to" << fontSize;
|
||||
|
||||
QLabel::paintEvent(event);
|
||||
// LOG(true, "Paint delay" << ((float)timer.nsecsElapsed())/1000000.0 << " mS");
|
||||
}
|
||||
|
||||
float DynamicFontSizeLabel::getWidgetMaximumFontSize(QWidget *widget, const QString &text)
|
||||
{
|
||||
QFont font = widget->font();
|
||||
const QRect widgetRect = widget->contentsRect();
|
||||
const float widgetWidth = widgetRect.width();
|
||||
const float widgetHeight = widgetRect.height();
|
||||
|
||||
QRectF newFontSizeRect;
|
||||
float currentSize = font.pointSizeF();
|
||||
|
||||
float step = currentSize / 2.0;
|
||||
|
||||
/* If too small, increase step */
|
||||
if (step <= FONT_PRECISION) {
|
||||
step = FONT_PRECISION * 4.0;
|
||||
}
|
||||
|
||||
float lastTestedSize = currentSize;
|
||||
|
||||
float currentHeight = 0;
|
||||
float currentWidth = 0;
|
||||
if (text == "") {
|
||||
return currentSize;
|
||||
}
|
||||
|
||||
if (currentSize < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Only stop when step is small enough and new size is smaller than QWidget */
|
||||
while (step > FONT_PRECISION || (currentHeight > widgetHeight) || (currentWidth > widgetWidth)) {
|
||||
/* Keep last tested value */
|
||||
lastTestedSize = currentSize;
|
||||
|
||||
/* Test label with its font */
|
||||
font.setPointSizeF(currentSize);
|
||||
/* Use font metrics to test */
|
||||
QFontMetricsF fm(font);
|
||||
|
||||
/* Check if widget is QLabel */
|
||||
QLabel *label = qobject_cast<QLabel *>(widget);
|
||||
if (label) {
|
||||
newFontSizeRect =
|
||||
fm.boundingRect(widgetRect, (label->wordWrap() ? Qt::TextWordWrap : 0) | label->alignment(), text);
|
||||
} else {
|
||||
newFontSizeRect = fm.boundingRect(widgetRect, 0, text);
|
||||
}
|
||||
|
||||
currentHeight = newFontSizeRect.height();
|
||||
currentWidth = newFontSizeRect.width();
|
||||
|
||||
/* If new font size is too big, decrease it */
|
||||
if ((currentHeight > widgetHeight) || (currentWidth > widgetWidth)) {
|
||||
// qDebug() << "-- contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect"
|
||||
// << newFontSizeRect << "Tight" << text << currentSize;
|
||||
currentSize -= step;
|
||||
/* if step is small enough, keep it constant, so it converge to biggest font size */
|
||||
if (step > FONT_PRECISION) {
|
||||
step /= 2.0;
|
||||
}
|
||||
/* Do not allow negative size */
|
||||
if (currentSize <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* If new font size is smaller than maximum possible size, increase it */
|
||||
else {
|
||||
// qDebug() << "++ contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect"
|
||||
// << newFontSizeRect << "Tight" << text << currentSize;
|
||||
currentSize += step;
|
||||
}
|
||||
}
|
||||
return lastTestedSize;
|
||||
}
|
||||
|
||||
void DynamicFontSizeLabel::setTextColor(QColor color)
|
||||
{
|
||||
if (color.isValid() && color != textColor) {
|
||||
textColor = color;
|
||||
setStyleSheet("color : " + color.name() + ";");
|
||||
}
|
||||
}
|
||||
|
||||
QColor DynamicFontSizeLabel::getTextColor()
|
||||
{
|
||||
return textColor;
|
||||
}
|
||||
|
||||
void DynamicFontSizeLabel::setTextAndColor(const QString &text, QColor color)
|
||||
{
|
||||
setTextColor(color);
|
||||
setText(text);
|
||||
}
|
||||
|
||||
/* Do not give any size hint as it it changes during paintEvent */
|
||||
QSize DynamicFontSizeLabel::minimumSizeHint() const
|
||||
{
|
||||
return QWidget::minimumSizeHint();
|
||||
}
|
||||
|
||||
/* Do not give any size hint as it it changes during paintEvent */
|
||||
QSize DynamicFontSizeLabel::sizeHint() const
|
||||
{
|
||||
return QWidget::sizeHint();
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef DYNAMICFONTSIZELABEL_H
|
||||
#define DYNAMICFONTSIZELABEL_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QLabel>
|
||||
|
||||
class DynamicFontSizeLabel : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DynamicFontSizeLabel(QWidget *parent = NULL, Qt::WindowFlags f = Qt::WindowFlags());
|
||||
|
||||
~DynamicFontSizeLabel()
|
||||
{
|
||||
}
|
||||
|
||||
static float getWidgetMaximumFontSize(QWidget *widget, const QString &text);
|
||||
|
||||
/* This method overwrite stylesheet */
|
||||
void setTextColor(QColor color);
|
||||
QColor getTextColor();
|
||||
void setTextAndColor(const QString &text, QColor color = QColor::Invalid);
|
||||
signals:
|
||||
void clicked();
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
QColor textColor;
|
||||
|
||||
// QWidget interface
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event);
|
||||
|
||||
// QWidget interface
|
||||
public:
|
||||
QSize minimumSizeHint() const;
|
||||
QSize sizeHint() const;
|
||||
};
|
||||
|
||||
#endif // DYNAMICFONTSIZELABEL_H
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
#include "dynamic_font_size_push_button.h"
|
||||
|
||||
#include "dynamic_font_size_label.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
|
||||
DynamicFontSizePushButton::DynamicFontSizePushButton(QWidget *parent) : QPushButton(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void DynamicFontSizePushButton::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
// Call the base class paintEvent to preserve any other painting behavior
|
||||
QPushButton::paintEvent(event);
|
||||
|
||||
// Adjust the font size dynamically based on the text
|
||||
QFont newFont = font();
|
||||
float fontSize = DynamicFontSizeLabel::getWidgetMaximumFontSize(this, this->text());
|
||||
newFont.setPointSizeF(fontSize);
|
||||
setFont(newFont);
|
||||
|
||||
// Get painter for custom painting
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Paint the background with a linear gradient (normal state)
|
||||
QLinearGradient gradient(0, 0, 0, height());
|
||||
if (isDown()) {
|
||||
// Pressed state
|
||||
gradient.setColorAt(0, QColor(128, 128, 128));
|
||||
gradient.setColorAt(1, QColor(64, 64, 64));
|
||||
} else if (underMouse()) {
|
||||
// Hover state
|
||||
gradient.setColorAt(0, QColor(96, 96, 96));
|
||||
gradient.setColorAt(1, QColor(48, 48, 48));
|
||||
} else {
|
||||
// Normal state
|
||||
gradient.setColorAt(0, QColor(64, 64, 64)); // start color
|
||||
gradient.setColorAt(1, QColor(32, 32, 32)); // end color
|
||||
}
|
||||
painter.setBrush(gradient);
|
||||
painter.setPen(Qt::NoPen); // No border
|
||||
painter.drawRect(rect());
|
||||
|
||||
// Paint the button text
|
||||
painter.setPen(QPen(textColor.isValid() ? textColor : QColor(255, 255, 255))); // Set text color
|
||||
painter.drawText(rect(), Qt::AlignCenter, text());
|
||||
}
|
||||
|
||||
void DynamicFontSizePushButton::setTextColor(QColor color)
|
||||
{
|
||||
if (color.isValid() && color != textColor) {
|
||||
textColor = color;
|
||||
update(); // Request a repaint to update the text color
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicFontSizePushButton::setTextAndColor(const QString &text, QColor color)
|
||||
{
|
||||
setTextColor(color);
|
||||
setText(text);
|
||||
}
|
||||
|
||||
QColor DynamicFontSizePushButton::getTextColor()
|
||||
{
|
||||
return textColor;
|
||||
}
|
||||
|
||||
/* Do not give any size hint as it it changes during paintEvent */
|
||||
QSize DynamicFontSizePushButton::minimumSizeHint() const
|
||||
{
|
||||
return QWidget::minimumSizeHint();
|
||||
}
|
||||
|
||||
/* Do not give any size hint as it it changes during paintEvent */
|
||||
QSize DynamicFontSizePushButton::sizeHint() const
|
||||
{
|
||||
return QWidget::sizeHint();
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef DYNAMICFONTSIZEPUSHBUTTON_H
|
||||
#define DYNAMICFONTSIZEPUSHBUTTON_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QPushButton>
|
||||
#include <QWidget>
|
||||
|
||||
class DynamicFontSizePushButton : public QPushButton
|
||||
{
|
||||
public:
|
||||
explicit DynamicFontSizePushButton(QWidget *parent = NULL);
|
||||
|
||||
/* This method overwrite stylesheet */
|
||||
void setTextColor(QColor color);
|
||||
QColor getTextColor();
|
||||
void setTextAndColor(const QString &text, QColor color = QColor::Invalid);
|
||||
|
||||
// QWidget interface
|
||||
QSize minimumSizeHint() const;
|
||||
QSize sizeHint() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event);
|
||||
|
||||
private:
|
||||
QColor textColor;
|
||||
};
|
||||
|
||||
#endif // DYNAMICFONTSIZEPUSHBUTTON_H
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#include "labeled_input.h"
|
||||
|
||||
LabeledInput::LabeledInput(QWidget *parent, const QString &labelText) : QWidget(parent)
|
||||
{
|
||||
label = new QLabel(labelText, this);
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->addWidget(label);
|
||||
}
|
||||
|
||||
QSpinBox *LabeledInput::addSpinBox(const int minValue, const int maxValue, const int defaultValue)
|
||||
{
|
||||
auto *spinBox = new QSpinBox(this);
|
||||
spinBox->setRange(minValue, maxValue);
|
||||
spinBox->setValue(defaultValue);
|
||||
layout->addWidget(spinBox);
|
||||
connect(spinBox, SIGNAL(valueChanged(int)), this, SIGNAL(spinBoxValueChanged(int)));
|
||||
return spinBox;
|
||||
}
|
||||
|
||||
// Add a QComboBox (for arbitrary selections)
|
||||
QComboBox *LabeledInput::addComboBox(const QStringList &items, const QString &defaultItem)
|
||||
{
|
||||
auto *comboBox = new QComboBox(this);
|
||||
comboBox->addItems(items);
|
||||
if (!defaultItem.isEmpty()) {
|
||||
comboBox->setCurrentText(defaultItem);
|
||||
}
|
||||
layout->addWidget(comboBox);
|
||||
return comboBox;
|
||||
}
|
||||
|
||||
// Add a QComboBox specifically for Qt Directions
|
||||
QComboBox *LabeledInput::addDirectionComboBox()
|
||||
{
|
||||
const QStringList directions = {"Qt::Horizontal", "Qt::Vertical"};
|
||||
const auto comboBox = addComboBox(directions, "Qt::Vertical");
|
||||
connect(comboBox, SIGNAL(currentTextChanged(QString)), this, SIGNAL(directionComboBoxChanged(QString)));
|
||||
return comboBox;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef LABELED_INPUT_H
|
||||
#define LABELED_INPUT_H
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QSpinBox>
|
||||
#include <QWidget>
|
||||
|
||||
class LabeledInput final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LabeledInput(QWidget *parent, const QString &labelText);
|
||||
|
||||
// Add a QSpinBox (for arbitrary numbers)
|
||||
QSpinBox *addSpinBox(int minValue, int maxValue, int defaultValue = 0);
|
||||
|
||||
// Add a QComboBox (for arbitrary selections)
|
||||
QComboBox *addComboBox(const QStringList &items, const QString &defaultItem = QString());
|
||||
|
||||
// Add a QComboBox specifically for Qt Directions
|
||||
QComboBox *addDirectionComboBox();
|
||||
|
||||
signals:
|
||||
void spinBoxValueChanged(int newValue); // Declare the valueChanged signal
|
||||
void comboBoxValueChanged(int newValue);
|
||||
void directionComboBoxChanged(QString newDirection);
|
||||
|
||||
private:
|
||||
QLabel *label;
|
||||
QHBoxLayout *layout;
|
||||
};
|
||||
|
||||
#endif // LABELED_INPUT_H
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#include "percent_bar_widget.h"
|
||||
|
||||
PercentBarWidget::PercentBarWidget(QWidget *parent, double initialValue) : QWidget(parent), valueToDisplay(initialValue)
|
||||
{
|
||||
setMinimumSize(50, 10);
|
||||
}
|
||||
|
||||
void PercentBarWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QPainter painter(this);
|
||||
QRect rect = this->rect();
|
||||
|
||||
const int midX = rect.width() / 2;
|
||||
const int height = rect.height();
|
||||
|
||||
// Draw background border (no fill)
|
||||
painter.setPen(QPen(Qt::black, 1));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawRect(rect.adjusted(0, 0, -1, -1)); // Avoid right/bottom overflow
|
||||
|
||||
const double halfWidth = rect.width() / 2.0;
|
||||
|
||||
const int barLength = static_cast<int>((qAbs(valueToDisplay) / 100.0) * halfWidth);
|
||||
|
||||
QRect fillRect;
|
||||
if (valueToDisplay > 0.0) {
|
||||
fillRect = QRect(midX, 0, barLength, height);
|
||||
painter.fillRect(fillRect, Qt::green);
|
||||
} else if (valueToDisplay < 0.0) {
|
||||
fillRect = QRect(midX - barLength, 0, barLength, height);
|
||||
painter.fillRect(fillRect, Qt::red);
|
||||
}
|
||||
|
||||
// Draw center line at 0
|
||||
painter.fillRect(midX - 1, 0, 3, height, Qt::white);
|
||||
|
||||
// Draw tick marks every 10%
|
||||
const int tickHeight = 4;
|
||||
|
||||
for (int percent = -100; percent <= 100; percent += 10) {
|
||||
int x = midX + static_cast<int>((percent / 100.0) * halfWidth);
|
||||
painter.drawLine(x, height - tickHeight, x, height);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef PERCENT_BAR_WIDGET_H
|
||||
#define PERCENT_BAR_WIDGET_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QPainter>
|
||||
#include <QWidget>
|
||||
|
||||
class PercentBarWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PercentBarWidget(QWidget *parent, double initialValue);
|
||||
|
||||
void setValue(double newValue)
|
||||
{
|
||||
valueToDisplay = qBound(-100.0, newValue, 100.0); // Clamp to [-100, 100]
|
||||
update(); // Trigger repaint
|
||||
}
|
||||
|
||||
double value() const
|
||||
{
|
||||
return valueToDisplay;
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
double valueToDisplay; // Ranges from -100 to 100
|
||||
};
|
||||
|
||||
#endif // PERCENT_BAR_WIDGET_H
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
#include "shadow_background_label.h"
|
||||
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
|
||||
/**
|
||||
* @class ShadowBackgroundLabel
|
||||
* @brief A QLabel with a semi-transparent black shadowed background and rounded corners.
|
||||
*
|
||||
* This label provides a styled appearance with centered white text and a translucent
|
||||
* rounded background, making it suitable for overlay or emphasis in a UI.
|
||||
*/
|
||||
ShadowBackgroundLabel::ShadowBackgroundLabel(QWidget *parent, const QString &text) : QLabel(parent)
|
||||
{
|
||||
setAttribute(Qt::WA_TranslucentBackground); // Allows transparency.
|
||||
setText("<font color='white'>" + text + "</font>"); ///< Ensures the text is rendered in white.
|
||||
setAlignment(Qt::AlignCenter); ///< Centers the text within the label.
|
||||
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); ///< Ensures minimum size constraints.
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles resizing of the label.
|
||||
*
|
||||
* Ensures the label updates its appearance when resized by triggering a repaint.
|
||||
*
|
||||
* @param event The resize event containing new size information.
|
||||
*/
|
||||
void ShadowBackgroundLabel::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QLabel::resizeEvent(event);
|
||||
update(); // Repaint borders explicitly.
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Custom paint event for drawing the label's background.
|
||||
*
|
||||
* Renders a semi-transparent black rounded rectangle as the background
|
||||
* and then delegates text rendering to QLabel.
|
||||
*
|
||||
* @param event The paint event for the widget.
|
||||
*/
|
||||
void ShadowBackgroundLabel::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
// Enable antialiasing for smoother edges.
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
// Set semi-transparent black brush and disable border pen.
|
||||
painter.setBrush(QColor(0, 0, 0, 128)); // Semi-transparent black.
|
||||
painter.setPen(Qt::NoPen); // No border.
|
||||
|
||||
// Adjust the rectangle to account for margins.
|
||||
QRect adjustedRect = this->rect();
|
||||
int margin = contentsMargins().left(); // Assuming equal margins.
|
||||
adjustedRect.adjust(margin, margin, -margin, -margin);
|
||||
|
||||
// Draw a rounded rectangle with a corner radius of 5.
|
||||
painter.drawRoundedRect(adjustedRect, 5, 5);
|
||||
|
||||
// Delegate text rendering to QLabel.
|
||||
QLabel::paintEvent(event);
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef STYLEDLABEL_H
|
||||
#define STYLEDLABEL_H
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
class ShadowBackgroundLabel : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ShadowBackgroundLabel(QWidget *parent, const QString &text);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override; // Custom painting logic
|
||||
};
|
||||
|
||||
#endif // STYLEDLABEL_H
|
||||
105
cockatrice/src/interface/widgets/general/home_styled_button.cpp
Normal file
105
cockatrice/src/interface/widgets/general/home_styled_button.cpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#include "home_styled_button.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <qgraphicseffect.h>
|
||||
#include <qstyleoption.h>
|
||||
|
||||
HomeStyledButton::HomeStyledButton(const QString &text, QPair<QColor, QColor> _gradientColors, QWidget *parent)
|
||||
: QPushButton(text, parent), gradientColors(_gradientColors)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
setMinimumHeight(50);
|
||||
setStyleSheet(generateButtonStylesheet(gradientColors));
|
||||
}
|
||||
|
||||
void HomeStyledButton::updateStylesheet(const QPair<QColor, QColor> &colors)
|
||||
{
|
||||
gradientColors = colors;
|
||||
setStyleSheet(generateButtonStylesheet(gradientColors));
|
||||
}
|
||||
|
||||
QString HomeStyledButton::generateButtonStylesheet(const QPair<QColor, QColor> &colors)
|
||||
{
|
||||
QColor baseGradientStart = colors.first;
|
||||
QColor baseGradientEnd = colors.second;
|
||||
|
||||
QColor hoverGradientStart = baseGradientStart.lighter(120); // 20% lighter
|
||||
QColor hoverGradientEnd = baseGradientEnd.lighter(120);
|
||||
|
||||
QColor pressedGradientStart = baseGradientStart.darker(130); // 30% darker
|
||||
QColor pressedGradientEnd = baseGradientEnd.darker(130);
|
||||
|
||||
// Disabled: more gray, less saturated
|
||||
QColor disabledGradientStart = baseGradientStart.toHsv();
|
||||
disabledGradientStart.setHsv(disabledGradientStart.hue(), disabledGradientStart.saturation() * 0.2,
|
||||
disabledGradientStart.value() * 0.6);
|
||||
QColor disabledGradientEnd = baseGradientEnd.toHsv();
|
||||
disabledGradientEnd.setHsv(disabledGradientEnd.hue(), disabledGradientEnd.saturation() * 0.2,
|
||||
disabledGradientEnd.value() * 0.6);
|
||||
|
||||
return QString(R"(
|
||||
QPushButton {
|
||||
font-size: 34px;
|
||||
padding: 30px;
|
||||
color: white;
|
||||
border: 2px solid %1;
|
||||
border-radius: 20px;
|
||||
background: qlineargradient(x1:0, y1:1, x2:0, y2:0,
|
||||
stop:0 %2, stop:1 %3);
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: qlineargradient(x1:0, y1:1, x2:0, y2:0,
|
||||
stop:0 %4, stop:1 %5);
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 %6, stop:1 %7);
|
||||
}
|
||||
QPushButton:disabled {
|
||||
color: #aaaaaa;
|
||||
border: 2px solid #888888;
|
||||
background: qlineargradient(x1:0, y1:1, x2:0, y2:0,
|
||||
stop:0 %8, stop:1 %9);
|
||||
}
|
||||
)")
|
||||
.arg(baseGradientStart.name())
|
||||
.arg(baseGradientStart.name())
|
||||
.arg(baseGradientEnd.name())
|
||||
.arg(hoverGradientStart.name())
|
||||
.arg(hoverGradientEnd.name())
|
||||
.arg(pressedGradientStart.name())
|
||||
.arg(pressedGradientEnd.name())
|
||||
.arg(disabledGradientStart.name())
|
||||
.arg(disabledGradientEnd.name());
|
||||
}
|
||||
|
||||
void HomeStyledButton::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event); // Event is just used for update clipping, we redraw the whole widget.
|
||||
QStyleOptionButton opt;
|
||||
initStyleOption(&opt);
|
||||
opt.text.clear(); // prevent style from drawing text
|
||||
|
||||
QPainter painter(this);
|
||||
style()->drawControl(QStyle::CE_PushButton, &opt, &painter, this);
|
||||
|
||||
// Draw white text with a black outline
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::TextAntialiasing);
|
||||
|
||||
QFont font = this->font();
|
||||
font.setBold(true);
|
||||
painter.setFont(font);
|
||||
|
||||
QFontMetrics fm(font);
|
||||
QSize textSize = fm.size(Qt::TextSingleLine, this->text());
|
||||
QPointF center((width() - textSize.width()) / 2.0, (height() + textSize.height() / 2.0) / 2.0);
|
||||
|
||||
QPainterPath path;
|
||||
path.addText(center, font, this->text());
|
||||
|
||||
painter.setPen(QPen(Qt::black, 2.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
|
||||
painter.setBrush(Qt::white);
|
||||
painter.drawPath(path);
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// Created by ascor on 6/15/25.
|
||||
//
|
||||
|
||||
#ifndef HOME_STYLED_BUTTON_H
|
||||
#define HOME_STYLED_BUTTON_H
|
||||
#include <QPushButton>
|
||||
|
||||
class HomeStyledButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
HomeStyledButton(const QString &text, QPair<QColor, QColor> gradientColors, QWidget *parent = nullptr);
|
||||
void updateStylesheet(const QPair<QColor, QColor> &colors);
|
||||
QString generateButtonStylesheet(const QPair<QColor, QColor> &colors);
|
||||
public slots:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
QPair<QColor, QColor> gradientColors;
|
||||
};
|
||||
|
||||
#endif // HOME_STYLED_BUTTON_H
|
||||
313
cockatrice/src/interface/widgets/general/home_widget.cpp
Normal file
313
cockatrice/src/interface/widgets/general/home_widget.cpp
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
#include "home_widget.h"
|
||||
|
||||
#include "../../../database/card_database_manager.h"
|
||||
#include "../../../server/remote/remote_client.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
#include "../../../tabs/tab_supervisor.h"
|
||||
#include "../../window_main.h"
|
||||
#include "background_sources.h"
|
||||
#include "home_styled_button.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
|
||||
: QWidget(parent), tabSupervisor(_tabSupervisor), background("theme:backgrounds/home"), overlay("theme:cockatrice")
|
||||
{
|
||||
layout = new QGridLayout(this);
|
||||
|
||||
backgroundSourceCard = new CardInfoPictureArtCropWidget(this);
|
||||
backgroundSourceDeck = new DeckLoader();
|
||||
|
||||
backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod",
|
||||
DeckLoader::CockatriceFormat, false);
|
||||
|
||||
gradientColors = extractDominantColors(background);
|
||||
|
||||
layout->addWidget(createButtons(), 1, 1, Qt::AlignVCenter | Qt::AlignHCenter);
|
||||
|
||||
layout->setRowStretch(0, 1);
|
||||
layout->setRowStretch(2, 1);
|
||||
layout->setColumnStretch(0, 1);
|
||||
layout->setColumnStretch(2, 1);
|
||||
|
||||
setLayout(layout);
|
||||
|
||||
cardChangeTimer = new QTimer(this);
|
||||
connect(cardChangeTimer, &QTimer::timeout, this, &HomeWidget::updateRandomCard);
|
||||
|
||||
initializeBackgroundFromSource();
|
||||
|
||||
updateConnectButton(tabSupervisor->getClient()->getStatus());
|
||||
|
||||
connect(tabSupervisor->getClient(), &RemoteClient::statusChanged, this, &HomeWidget::updateConnectButton);
|
||||
connect(&SettingsCache::instance(), &SettingsCache::homeTabBackgroundSourceChanged, this,
|
||||
&HomeWidget::initializeBackgroundFromSource);
|
||||
connect(&SettingsCache::instance(), &SettingsCache::homeTabBackgroundShuffleFrequencyChanged, this,
|
||||
&HomeWidget::onBackgroundShuffleFrequencyChanged);
|
||||
}
|
||||
|
||||
void HomeWidget::initializeBackgroundFromSource()
|
||||
{
|
||||
if (CardDatabaseManager::getInstance()->getLoadStatus() != LoadStatus::Ok) {
|
||||
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
|
||||
&HomeWidget::initializeBackgroundFromSource);
|
||||
return;
|
||||
}
|
||||
|
||||
auto backgroundSourceType = BackgroundSources::fromId(SettingsCache::instance().getHomeTabBackgroundSource());
|
||||
|
||||
cardChangeTimer->stop();
|
||||
|
||||
switch (backgroundSourceType) {
|
||||
case BackgroundSources::Theme:
|
||||
background = QPixmap("theme:backgrounds/home");
|
||||
updateButtonsToBackgroundColor();
|
||||
update();
|
||||
break;
|
||||
case BackgroundSources::RandomCardArt:
|
||||
cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000);
|
||||
break;
|
||||
case BackgroundSources::DeckFileArt:
|
||||
backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod",
|
||||
DeckLoader::CockatriceFormat, false);
|
||||
cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWidget::updateRandomCard()
|
||||
{
|
||||
auto backgroundSourceType = BackgroundSources::fromId(SettingsCache::instance().getHomeTabBackgroundSource());
|
||||
|
||||
ExactCard newCard;
|
||||
|
||||
switch (backgroundSourceType) {
|
||||
case BackgroundSources::Theme:
|
||||
break;
|
||||
case BackgroundSources::RandomCardArt:
|
||||
do {
|
||||
newCard = CardDatabaseManager::getInstance()->getRandomCard();
|
||||
} while (newCard == backgroundSourceCard->getCard() &&
|
||||
newCard.getCardPtr()->getProperty("layout") != "normal");
|
||||
break;
|
||||
case BackgroundSources::DeckFileArt:
|
||||
QList<CardRef> cardRefs = backgroundSourceDeck->getCardRefList();
|
||||
ExactCard oldCard = backgroundSourceCard->getCard();
|
||||
|
||||
if (!cardRefs.empty()) {
|
||||
if (cardRefs.size() == 1) {
|
||||
newCard = CardDatabaseManager::getInstance()->getCard(cardRefs.first());
|
||||
} else {
|
||||
// Keep picking until different
|
||||
do {
|
||||
int idx = QRandomGenerator::global()->bounded(cardRefs.size());
|
||||
newCard = CardDatabaseManager::getInstance()->getCard(cardRefs.at(idx));
|
||||
} while (newCard == oldCard);
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
newCard = CardDatabaseManager::getInstance()->getRandomCard();
|
||||
} while (newCard == oldCard);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!newCard)
|
||||
return;
|
||||
|
||||
connect(newCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, &HomeWidget::updateBackgroundProperties);
|
||||
backgroundSourceCard->setCard(newCard);
|
||||
background = backgroundSourceCard->getProcessedBackground(size());
|
||||
|
||||
if (SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() <= 0) {
|
||||
cardChangeTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWidget::onBackgroundShuffleFrequencyChanged()
|
||||
{
|
||||
cardChangeTimer->stop();
|
||||
if (SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() <= 0) {
|
||||
return;
|
||||
}
|
||||
cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000);
|
||||
}
|
||||
|
||||
void HomeWidget::updateBackgroundProperties()
|
||||
{
|
||||
background = backgroundSourceCard->getProcessedBackground(size());
|
||||
updateButtonsToBackgroundColor();
|
||||
update(); // Triggers repaint
|
||||
}
|
||||
|
||||
void HomeWidget::updateButtonsToBackgroundColor()
|
||||
{
|
||||
gradientColors = extractDominantColors(background);
|
||||
for (HomeStyledButton *button : findChildren<HomeStyledButton *>()) {
|
||||
button->updateStylesheet(gradientColors);
|
||||
button->update();
|
||||
}
|
||||
}
|
||||
|
||||
QGroupBox *HomeWidget::createButtons()
|
||||
{
|
||||
QGroupBox *box = new QGroupBox(this);
|
||||
box->setStyleSheet(R"(
|
||||
QGroupBox {
|
||||
font-size: 20px;
|
||||
color: white; /* Title text color */
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QGroupBox::title {
|
||||
color: white;
|
||||
subcontrol-origin: margin;
|
||||
subcontrol-position: top center; /* or top left / right */
|
||||
}
|
||||
)");
|
||||
box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
QVBoxLayout *boxLayout = new QVBoxLayout;
|
||||
boxLayout->setAlignment(Qt::AlignHCenter);
|
||||
|
||||
QLabel *logoLabel = new QLabel;
|
||||
logoLabel->setPixmap(overlay.scaledToWidth(200, Qt::SmoothTransformation));
|
||||
logoLabel->setAlignment(Qt::AlignCenter);
|
||||
boxLayout->addWidget(logoLabel);
|
||||
boxLayout->addSpacing(25);
|
||||
|
||||
connectButton = new HomeStyledButton("Connect/Play", gradientColors);
|
||||
boxLayout->addWidget(connectButton, 1);
|
||||
|
||||
auto visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors);
|
||||
connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor,
|
||||
[this] { tabSupervisor->openDeckInNewTab(nullptr); });
|
||||
boxLayout->addWidget(visualDeckEditorButton);
|
||||
auto visualDeckStorageButton = new HomeStyledButton(tr("Browse Decks"), gradientColors);
|
||||
connect(visualDeckStorageButton, &QPushButton::clicked, tabSupervisor,
|
||||
[this] { tabSupervisor->actTabVisualDeckStorage(true); });
|
||||
boxLayout->addWidget(visualDeckStorageButton);
|
||||
auto visualDatabaseDisplayButton = new HomeStyledButton(tr("Browse Card Database"), gradientColors);
|
||||
connect(visualDatabaseDisplayButton, &QPushButton::clicked, tabSupervisor,
|
||||
&TabSupervisor::addVisualDatabaseDisplayTab);
|
||||
boxLayout->addWidget(visualDatabaseDisplayButton);
|
||||
auto edhrecButton = new HomeStyledButton(tr("Browse EDHRec"), gradientColors);
|
||||
connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab);
|
||||
boxLayout->addWidget(edhrecButton);
|
||||
auto replaybutton = new HomeStyledButton(tr("View Replays"), gradientColors);
|
||||
connect(replaybutton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); });
|
||||
boxLayout->addWidget(replaybutton);
|
||||
if (qobject_cast<MainWindow *>(tabSupervisor->parentWidget())) {
|
||||
auto exitButton = new HomeStyledButton(tr("Quit"), gradientColors);
|
||||
connect(exitButton, &QPushButton::clicked, qobject_cast<MainWindow *>(tabSupervisor->parentWidget()),
|
||||
&MainWindow::actExit);
|
||||
boxLayout->addWidget(exitButton);
|
||||
}
|
||||
|
||||
box->setLayout(boxLayout);
|
||||
return box;
|
||||
}
|
||||
|
||||
void HomeWidget::updateConnectButton(const ClientStatus status)
|
||||
{
|
||||
disconnect(connectButton, &QPushButton::clicked, nullptr, nullptr);
|
||||
switch (status) {
|
||||
case StatusConnecting:
|
||||
connectButton->setText(tr("Connecting..."));
|
||||
connectButton->setEnabled(false);
|
||||
break;
|
||||
case StatusDisconnected:
|
||||
connectButton->setText(tr("Connect"));
|
||||
connectButton->setEnabled(true);
|
||||
connect(connectButton, &QPushButton::clicked, qobject_cast<MainWindow *>(tabSupervisor->parentWidget()),
|
||||
&MainWindow::actConnect);
|
||||
break;
|
||||
case StatusLoggedIn:
|
||||
connectButton->setText(tr("Play"));
|
||||
connectButton->setEnabled(true);
|
||||
connect(connectButton, &QPushButton::clicked, tabSupervisor,
|
||||
&TabSupervisor::switchToFirstAvailableNetworkTab);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QPair<QColor, QColor> HomeWidget::extractDominantColors(const QPixmap &pixmap)
|
||||
{
|
||||
if (SettingsCache::instance().getThemeName() == "Default" &&
|
||||
SettingsCache::instance().getHomeTabBackgroundSource() == BackgroundSources::toId(BackgroundSources::Theme)) {
|
||||
return QPair<QColor, QColor>(QColor::fromRgb(20, 140, 60), QColor::fromRgb(120, 200, 80));
|
||||
}
|
||||
|
||||
// Step 1: Downscale image for performance
|
||||
QImage image = pixmap.toImage()
|
||||
.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)
|
||||
.convertToFormat(QImage::Format_RGB32);
|
||||
|
||||
QMap<QRgb, int> colorCount;
|
||||
|
||||
// Step 2: Count quantized colors
|
||||
for (int y = 0; y < image.height(); ++y) {
|
||||
const QRgb *scanLine = reinterpret_cast<const QRgb *>(image.scanLine(y));
|
||||
for (int x = 0; x < image.width(); ++x) {
|
||||
QColor color = QColor::fromRgb(scanLine[x]);
|
||||
|
||||
int r = color.red() & 0xF0;
|
||||
int g = color.green() & 0xF0;
|
||||
int b = color.blue() & 0xF0;
|
||||
|
||||
QRgb quantized = qRgb(r, g, b);
|
||||
colorCount[quantized]++;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Sort by frequency
|
||||
QVector<QPair<QRgb, int>> sortedColors;
|
||||
for (auto it = colorCount.constBegin(); it != colorCount.constEnd(); ++it) {
|
||||
sortedColors.append(qMakePair(it.key(), it.value()));
|
||||
}
|
||||
|
||||
std::sort(sortedColors.begin(), sortedColors.end(),
|
||||
[](const QPair<QRgb, int> &a, const QPair<QRgb, int> &b) { return a.second > b.second; });
|
||||
|
||||
// Step 4: Pick top two distinct colors
|
||||
QColor first = QColor(sortedColors.value(0).first);
|
||||
QColor second = first;
|
||||
|
||||
for (int i = 1; i < sortedColors.size(); ++i) {
|
||||
QColor candidate = QColor(sortedColors[i].first);
|
||||
if (candidate != first) {
|
||||
second = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return QPair<QColor, QColor>(first, second);
|
||||
}
|
||||
|
||||
void HomeWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
background = background.scaled(size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||
|
||||
// Draw already-scaled background centered
|
||||
QSize widgetSize = size();
|
||||
QSize bgSize = background.size();
|
||||
QPoint topLeft((widgetSize.width() - bgSize.width()) / 2, (widgetSize.height() - bgSize.height()) / 2);
|
||||
|
||||
painter.drawPixmap(topLeft, background);
|
||||
|
||||
// Draw translucent black overlay with rounded corners
|
||||
QRectF overlayRect(5, 5, width() - 10, height() - 10); // 5px inset
|
||||
QPainterPath roundedRectPath;
|
||||
roundedRectPath.addRoundedRect(overlayRect, 20, 20); // 20px corner radius
|
||||
|
||||
QColor semiTransparentBlack(0, 0, 0, static_cast<int>(255 * 0.33)); // 33% opacity
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.fillPath(roundedRectPath, semiTransparentBlack);
|
||||
|
||||
QWidget::paintEvent(event);
|
||||
}
|
||||
43
cockatrice/src/interface/widgets/general/home_widget.h
Normal file
43
cockatrice/src/interface/widgets/general/home_widget.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef HOME_WIDGET_H
|
||||
#define HOME_WIDGET_H
|
||||
#include "../../../server/abstract_client.h"
|
||||
#include "../../../tabs/tab_supervisor.h"
|
||||
#include "../cards/card_info_picture_art_crop_widget.h"
|
||||
#include "home_styled_button.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QWidget>
|
||||
|
||||
class HomeWidget : public QWidget
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
HomeWidget(QWidget *parent, TabSupervisor *tabSupervisor);
|
||||
void updateRandomCard();
|
||||
QPair<QColor, QColor> extractDominantColors(const QPixmap &pixmap);
|
||||
|
||||
public slots:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void initializeBackgroundFromSource();
|
||||
void onBackgroundShuffleFrequencyChanged();
|
||||
void updateBackgroundProperties();
|
||||
void updateButtonsToBackgroundColor();
|
||||
QGroupBox *createButtons();
|
||||
void updateConnectButton(const ClientStatus status);
|
||||
|
||||
private:
|
||||
QGridLayout *layout;
|
||||
QTimer *cardChangeTimer;
|
||||
TabSupervisor *tabSupervisor;
|
||||
QPixmap background;
|
||||
CardInfoPictureArtCropWidget *backgroundSourceCard = nullptr;
|
||||
DeckLoader *backgroundSourceDeck;
|
||||
QPixmap overlay;
|
||||
QPair<QColor, QColor> gradientColors;
|
||||
HomeStyledButton *connectButton;
|
||||
};
|
||||
|
||||
#endif // HOME_WIDGET_H
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
* @file flow_widget.cpp
|
||||
* @brief Implementation of the FlowWidget class for organizing widgets in a flow layout within a scrollable area.
|
||||
*/
|
||||
|
||||
#include "flow_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QResizeEvent>
|
||||
#include <QWidget>
|
||||
#include <qscrollarea.h>
|
||||
#include <qsizepolicy.h>
|
||||
|
||||
/**
|
||||
* @brief Constructs a FlowWidget with a scrollable layout.
|
||||
*
|
||||
* @param parent The parent widget of this FlowWidget.
|
||||
* @param horizontalPolicy The horizontal scroll bar policy for the scroll area.
|
||||
* @param verticalPolicy The vertical scroll bar policy for the scroll area.
|
||||
*/
|
||||
FlowWidget::FlowWidget(QWidget *parent,
|
||||
const Qt::Orientation _flowDirection,
|
||||
const Qt::ScrollBarPolicy horizontalPolicy,
|
||||
const Qt::ScrollBarPolicy verticalPolicy)
|
||||
: QWidget(parent), flowDirection(_flowDirection)
|
||||
{
|
||||
// Main Widget and Layout
|
||||
if (_flowDirection == Qt::Horizontal) {
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
setMinimumWidth(0);
|
||||
} else {
|
||||
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||
setMinimumHeight(0);
|
||||
}
|
||||
mainLayout = new QHBoxLayout(this);
|
||||
setLayout(mainLayout);
|
||||
|
||||
if (horizontalPolicy != Qt::ScrollBarAlwaysOff || verticalPolicy != Qt::ScrollBarAlwaysOff) {
|
||||
// Scroll Area, which should expand as much as possible, since it should be the only direct child widget.
|
||||
scrollArea = new QScrollArea(this);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
scrollArea->setMinimumSize(0, 0);
|
||||
scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
// Set scrollbar policies
|
||||
scrollArea->setHorizontalScrollBarPolicy(horizontalPolicy);
|
||||
scrollArea->setVerticalScrollBarPolicy(verticalPolicy);
|
||||
} else {
|
||||
scrollArea = nullptr;
|
||||
}
|
||||
|
||||
// Flow Layout inside the scroll area
|
||||
if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
|
||||
container = new QWidget(this);
|
||||
} else {
|
||||
container = new QWidget(scrollArea);
|
||||
}
|
||||
|
||||
flowLayout = new FlowLayout(container, flowDirection);
|
||||
|
||||
container->setLayout(flowLayout);
|
||||
// The container should expand as much as possible, trusting the scrollArea to constrain it.
|
||||
if (_flowDirection == Qt::Horizontal) {
|
||||
container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
container->setMinimumWidth(0);
|
||||
} else {
|
||||
container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
container->setMinimumHeight(0);
|
||||
}
|
||||
|
||||
// Use the FlowLayout container directly if we disable the ScrollArea
|
||||
if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
|
||||
mainLayout->addWidget(container);
|
||||
} else {
|
||||
scrollArea->setWidget(container);
|
||||
mainLayout->addWidget(scrollArea);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a widget to the flow layout within the FlowWidget.
|
||||
*
|
||||
* Adjusts the widget's size policy based on the scroll bar policies.
|
||||
*
|
||||
* @param widget_to_add The widget to add to the flow layout.
|
||||
*/
|
||||
void FlowWidget::addWidget(QWidget *widget_to_add) const
|
||||
{
|
||||
flowLayout->addWidget(widget_to_add);
|
||||
}
|
||||
|
||||
void FlowWidget::insertWidgetAtIndex(QWidget *toInsert, int index)
|
||||
{
|
||||
flowLayout->insertWidgetAtIndex(toInsert, index);
|
||||
update();
|
||||
}
|
||||
|
||||
void FlowWidget::removeWidget(QWidget *widgetToRemove) const
|
||||
{
|
||||
flowLayout->removeWidget(widgetToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears all widgets from the flow layout.
|
||||
*
|
||||
* Deletes each widget and layout item, and recreates the flow layout if it was removed.
|
||||
*/
|
||||
void FlowWidget::clearLayout()
|
||||
{
|
||||
if (flowLayout != nullptr) {
|
||||
QLayoutItem *item;
|
||||
while ((item = flowLayout->takeAt(0)) != nullptr) {
|
||||
item->widget()->deleteLater(); // Delete the widget
|
||||
delete item; // Delete the layout item
|
||||
}
|
||||
} else {
|
||||
flowLayout = new FlowLayout(container, flowDirection);
|
||||
container->setLayout(flowLayout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles resize events for the FlowWidget.
|
||||
*
|
||||
* Triggers layout recalculation and adjusts the scroll area content size.
|
||||
*
|
||||
* @param event The resize event containing the new size information.
|
||||
*/
|
||||
void FlowWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
qCDebug(FlowWidgetSizeLog) << event->size();
|
||||
|
||||
// Trigger the layout to recalculate
|
||||
if (flowLayout != nullptr) {
|
||||
flowLayout->invalidate(); // Marks the layout as dirty and requires recalculation
|
||||
flowLayout->activate(); // Recalculate the layout based on the new size
|
||||
}
|
||||
|
||||
// Ensure the scroll area and its content adjust correctly
|
||||
if (scrollArea != nullptr && scrollArea->widget() != nullptr) {
|
||||
qCDebug(FlowWidgetSizeLog) << "Got a scrollarea: " << scrollArea->widget()->size();
|
||||
scrollArea->widget()->adjustSize();
|
||||
} else {
|
||||
container->adjustSize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the minimum size for all widgets inside the FlowWidget to the maximum sizeHint of all of them.
|
||||
*/
|
||||
void FlowWidget::setMinimumSizeToMaxSizeHint()
|
||||
{
|
||||
QSize maxSize(0, 0); // Initialize to a zero size
|
||||
|
||||
// Iterate over all widgets in the flow layout to find the maximum sizeHint
|
||||
for (int i = 0; i < flowLayout->count(); ++i) {
|
||||
if (QLayoutItem *item = flowLayout->itemAt(i)) {
|
||||
if (QWidget *widget = item->widget()) {
|
||||
// Update the max size based on the sizeHint of each widget
|
||||
QSize widgetSizeHint = widget->sizeHint();
|
||||
maxSize.setWidth(qMax(maxSize.width(), widgetSizeHint.width()));
|
||||
maxSize.setHeight(qMax(maxSize.height(), widgetSizeHint.height()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the minimum size for all widgets to the max sizeHint
|
||||
for (int i = 0; i < flowLayout->count(); ++i) {
|
||||
if (QLayoutItem *item = flowLayout->itemAt(i)) {
|
||||
if (QWidget *widget = item->widget()) {
|
||||
widget->setMinimumSize(maxSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QLayoutItem *FlowWidget::itemAt(int index) const
|
||||
{
|
||||
return flowLayout->itemAt(index);
|
||||
}
|
||||
|
||||
int FlowWidget::count() const
|
||||
{
|
||||
return flowLayout->count();
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef FLOW_WIDGET_H
|
||||
#define FLOW_WIDGET_H
|
||||
#include "../../../layouts/flow_layout.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLoggingCategory>
|
||||
#include <QWidget>
|
||||
#include <qscrollarea.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(FlowWidgetLog, "flow_widget", QtInfoMsg);
|
||||
inline Q_LOGGING_CATEGORY(FlowWidgetSizeLog, "flow_widget.size", QtInfoMsg);
|
||||
|
||||
class FlowWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FlowWidget(QWidget *parent,
|
||||
Qt::Orientation orientation,
|
||||
Qt::ScrollBarPolicy horizontalPolicy,
|
||||
Qt::ScrollBarPolicy verticalPolicy);
|
||||
void addWidget(QWidget *widget_to_add) const;
|
||||
void insertWidgetAtIndex(QWidget *toInsert, int index);
|
||||
void removeWidget(QWidget *widgetToRemove) const;
|
||||
void clearLayout();
|
||||
[[nodiscard]] int count() const;
|
||||
[[nodiscard]] QLayoutItem *itemAt(int index) const;
|
||||
|
||||
QScrollArea *scrollArea;
|
||||
|
||||
public slots:
|
||||
void setMinimumSizeToMaxSizeHint();
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
Qt::Orientation flowDirection;
|
||||
QHBoxLayout *mainLayout;
|
||||
FlowLayout *flowLayout;
|
||||
QWidget *container;
|
||||
};
|
||||
|
||||
#endif // FLOW_WIDGET_H
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#include "overlap_control_widget.h"
|
||||
|
||||
#include "overlap_widget.h"
|
||||
|
||||
OverlapControlWidget::OverlapControlWidget(int overlapPercentage,
|
||||
int maxColumns,
|
||||
int maxRows,
|
||||
Qt::Orientation direction,
|
||||
QWidget *parent)
|
||||
: QWidget(parent), overlapPercentage(overlapPercentage), maxColumns(maxColumns), maxRows(maxRows),
|
||||
direction(direction)
|
||||
{
|
||||
// Main Widget and Layout
|
||||
this->setMinimumSize(0, 100);
|
||||
// this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
// this->setStyleSheet("border: 10px solid red;");
|
||||
|
||||
layout = new QHBoxLayout(this);
|
||||
this->setLayout(layout);
|
||||
|
||||
card_size_slider = new QSlider(Qt::Horizontal);
|
||||
card_size_slider->setRange(1, 10); // Example range for scaling, adjust as needed
|
||||
|
||||
amount_of_items_to_overlap = new LabeledInput(this, tr("Cards to overlap:"));
|
||||
amount_of_items_to_overlap->addSpinBox(0, 999, 10);
|
||||
overlap_percentage_input = new LabeledInput(this, tr("Overlap percentage:"));
|
||||
overlap_percentage_input->addSpinBox(0, 100, 80);
|
||||
overlap_direction = new LabeledInput(this, tr("Overlap direction:"));
|
||||
overlap_direction->addDirectionComboBox();
|
||||
|
||||
layout->addWidget(card_size_slider);
|
||||
layout->addWidget(amount_of_items_to_overlap);
|
||||
layout->addWidget(overlap_percentage_input);
|
||||
layout->addWidget(overlap_direction);
|
||||
|
||||
// TODO probably connect this to the parent
|
||||
// connect(card_size_slider, &QSlider::valueChanged, display, &CardPicture::setScaleFactor);
|
||||
}
|
||||
|
||||
void OverlapControlWidget::connectOverlapWidget(OverlapWidget *overlap_widget)
|
||||
{
|
||||
connect(amount_of_items_to_overlap, &LabeledInput::spinBoxValueChanged, overlap_widget,
|
||||
&OverlapWidget::maxOverlapItemsChanged);
|
||||
connect(overlap_direction, &LabeledInput::directionComboBoxChanged, overlap_widget,
|
||||
&OverlapWidget::overlapDirectionChanged);
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef OVERLAP_CONTROL_WIDGET_H
|
||||
#define OVERLAP_CONTROL_WIDGET_H
|
||||
#include "../display/labeled_input.h"
|
||||
#include "overlap_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QSlider>
|
||||
#include <QWidget>
|
||||
|
||||
class OverlapControlWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OverlapControlWidget(int overlapPercentage,
|
||||
int maxColumns,
|
||||
int maxRows,
|
||||
Qt::Orientation direction,
|
||||
QWidget *parent);
|
||||
void connectOverlapWidget(OverlapWidget *overlap_widget);
|
||||
|
||||
private:
|
||||
QHBoxLayout *layout;
|
||||
QSlider *card_size_slider;
|
||||
LabeledInput *amount_of_items_to_overlap;
|
||||
LabeledInput *overlap_percentage_input;
|
||||
LabeledInput *overlap_direction;
|
||||
int overlapPercentage;
|
||||
int maxColumns;
|
||||
int maxRows;
|
||||
Qt::Orientation direction;
|
||||
};
|
||||
|
||||
#endif // OVERLAP_CONTROL_WIDGET_H
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
#include "overlap_widget.h"
|
||||
|
||||
#include "../../../../deck/deck_list_model.h"
|
||||
#include "../../../layouts/flow_layout.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
/**
|
||||
* @class OverlapWidget
|
||||
* @brief A widget for managing overlapping child widgets.
|
||||
*
|
||||
* The OverlapWidget class is a QWidget subclass that utilizes the OverlapLayout
|
||||
* to arrange its child widgets in an overlapping manner. This widget allows
|
||||
* configuration of overlap percentage, maximum columns, maximum rows, and layout
|
||||
* direction, making it suitable for displaying elements that can partially stack
|
||||
* over each other. The widget automatically manages resizing and re-layout of its
|
||||
* child widgets based on the available space and specified parameters.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Constructs an OverlapWidget with specified layout parameters.
|
||||
*
|
||||
* Initializes the OverlapWidget with the given overlap percentage, maximum number
|
||||
* of columns and rows, and layout direction. Sets size policies to ensure the widget
|
||||
* can expand as needed. A new OverlapLayout is created and assigned to manage the
|
||||
* layout of child widgets.
|
||||
*
|
||||
* @param overlapPercentage The percentage of overlap between child widgets (0-100).
|
||||
* @param maxColumns The maximum number of columns for the layout (0 for unlimited).
|
||||
* @param maxRows The maximum number of rows for the layout (0 for unlimited).
|
||||
* @param direction The orientation of the layout, either Qt::Horizontal or Qt::Vertical.
|
||||
* @param adjustOnResize If the overlap widgets should adjust its max columns/rows on resize to fit.
|
||||
* @param parent The parent widget of this OverlapWidget.
|
||||
*/
|
||||
OverlapWidget::OverlapWidget(QWidget *parent,
|
||||
const int overlapPercentage,
|
||||
const int maxColumns,
|
||||
const int maxRows,
|
||||
const Qt::Orientation direction,
|
||||
const bool adjustOnResize)
|
||||
: QWidget(parent), overlapPercentage(overlapPercentage), maxColumns(maxColumns), maxRows(maxRows),
|
||||
direction(direction), adjustOnResize(adjustOnResize)
|
||||
{
|
||||
overlapLayout = new OverlapLayout(this, overlapPercentage, maxColumns, maxRows, direction, Qt::Horizontal);
|
||||
setLayout(overlapLayout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a widget to the overlap layout.
|
||||
*
|
||||
* This method appends the specified widget to the internal OverlapLayout, allowing
|
||||
* it to be arranged with the existing child widgets. The widget's visibility and
|
||||
* behavior will be managed by the layout.
|
||||
*
|
||||
* @param widgetToAdd A pointer to the QWidget to be added to the layout.
|
||||
*/
|
||||
void OverlapWidget::addWidget(QWidget *widgetToAdd) const
|
||||
{
|
||||
overlapLayout->addWidget(widgetToAdd);
|
||||
}
|
||||
|
||||
void OverlapWidget::insertWidgetAtIndex(QWidget *toInsert, int index)
|
||||
{
|
||||
overlapLayout->insertWidgetAtIndex(toInsert, index);
|
||||
update();
|
||||
}
|
||||
|
||||
void OverlapWidget::removeWidget(QWidget *widgetToRemove) const
|
||||
{
|
||||
overlapLayout->removeWidget(widgetToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears all widgets from the layout and deletes them.
|
||||
*
|
||||
* This method removes all child widgets from the OverlapLayout, deleting both the
|
||||
* widget instances and their corresponding layout items. This ensures that the layout
|
||||
* is empty and can be reused or refreshed as needed.
|
||||
*/
|
||||
void OverlapWidget::clearLayout()
|
||||
{
|
||||
if (overlapLayout != nullptr) {
|
||||
QLayoutItem *item;
|
||||
while ((item = overlapLayout->takeAt(0)) != nullptr) {
|
||||
item->widget()->deleteLater();
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
// If layout is null, create a new layout; otherwise, reuse the existing one
|
||||
if (overlapLayout == nullptr) {
|
||||
overlapLayout = new OverlapLayout(this, overlapPercentage, maxColumns, maxRows, direction);
|
||||
this->setLayout(overlapLayout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles resizing events for the widget.
|
||||
*
|
||||
* This overridden method is called when the widget is resized. It invokes layout
|
||||
* recalculation to ensure that the child widgets are correctly arranged based on the
|
||||
* new dimensions. It marks the layout as dirty and activates it to reflect the changes.
|
||||
*
|
||||
* @param event The resize event containing the new size information.
|
||||
*/
|
||||
void OverlapWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
// Trigger the layout to recalculate
|
||||
if (overlapLayout != nullptr) {
|
||||
overlapLayout->invalidate(); // Marks the layout as dirty and requires recalculation
|
||||
overlapLayout->activate(); // Recalculate the layout based on the new size
|
||||
}
|
||||
|
||||
if (adjustOnResize) {
|
||||
adjustMaxColumnsAndRows();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Dynamically adjusts maxColumns and maxRows based on widget size and layout direction.
|
||||
*
|
||||
* This function calculates the maximum number of columns or rows that can fit within
|
||||
* the widget's width and height, depending on the layout direction. It then updates
|
||||
* the OverlapLayout with these calculated values to ensure the layout is optimized.
|
||||
*/
|
||||
void OverlapWidget::adjustMaxColumnsAndRows()
|
||||
{
|
||||
if (direction == Qt::Vertical) {
|
||||
// Calculate columns based on width for vertical layout
|
||||
const int calculatedColumns = overlapLayout->calculateMaxColumns();
|
||||
maxColumns = calculatedColumns;
|
||||
overlapLayout->setMaxColumns(calculatedColumns);
|
||||
|
||||
// Calculate rows based on total item count and columns
|
||||
const int calculatedRows = overlapLayout->calculateRowsForColumns(calculatedColumns);
|
||||
maxRows = calculatedRows;
|
||||
overlapLayout->setMaxRows(calculatedRows);
|
||||
} else {
|
||||
// Calculate rows based on height for horizontal layout
|
||||
const int calculatedRows = overlapLayout->calculateMaxRows();
|
||||
maxRows = calculatedRows;
|
||||
overlapLayout->setMaxRows(calculatedRows);
|
||||
|
||||
// Calculate columns based on total item count and rows
|
||||
const int calculatedColumns = overlapLayout->calculateColumnsForRows(calculatedRows);
|
||||
maxColumns = calculatedColumns;
|
||||
overlapLayout->setMaxColumns(calculatedColumns);
|
||||
}
|
||||
|
||||
overlapLayout->invalidate();
|
||||
overlapLayout->activate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the maximum number of overlapping items based on new value.
|
||||
*
|
||||
* This method updates the maximum number of columns or rows for the overlap layout
|
||||
* based on the given new value. It adjusts the layout direction accordingly and
|
||||
* triggers a size adjustment for the widget, ensuring the layout reflects the changes.
|
||||
*
|
||||
* @param newValue The new maximum number of overlapping items allowed in the layout.
|
||||
*/
|
||||
void OverlapWidget::maxOverlapItemsChanged(const int newValue)
|
||||
{
|
||||
if (direction == Qt::Horizontal) {
|
||||
maxRows = 0;
|
||||
overlapLayout->setMaxRows(0);
|
||||
|
||||
maxColumns = newValue;
|
||||
overlapLayout->setMaxColumns(newValue);
|
||||
} else {
|
||||
maxRows = newValue;
|
||||
overlapLayout->setMaxRows(newValue);
|
||||
|
||||
maxColumns = 0;
|
||||
overlapLayout->setMaxColumns(0);
|
||||
}
|
||||
this->adjustSize();
|
||||
overlapLayout->invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Changes the layout direction based on the specified new direction.
|
||||
*
|
||||
* This method modifies the layout direction of the OverlapLayout based on the input
|
||||
* string. It updates the direction and triggers a size adjustment for the widget.
|
||||
* Valid inputs are "Qt::Horizontal" and "Qt::Vertical".
|
||||
*
|
||||
* @param newDirection The new layout direction as a QString.
|
||||
*/
|
||||
void OverlapWidget::overlapDirectionChanged(const QString &newDirection)
|
||||
{
|
||||
if (newDirection.compare("Qt::Horizontal", Qt::CaseInsensitive) == 0) {
|
||||
direction = Qt::Horizontal;
|
||||
overlapLayout->setDirection(direction);
|
||||
} else if (newDirection.compare("Qt::Vertical", Qt::CaseInsensitive) == 0) {
|
||||
direction = Qt::Vertical;
|
||||
overlapLayout->setDirection(direction);
|
||||
}
|
||||
this->adjustSize();
|
||||
overlapLayout->invalidate();
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef OVERLAP_WIDGET_H
|
||||
#define OVERLAP_WIDGET_H
|
||||
|
||||
#include "../../../layouts/overlap_layout.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class OverlapWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OverlapWidget(QWidget *parent,
|
||||
int overlapPercentage,
|
||||
int maxColumns,
|
||||
int maxRows,
|
||||
Qt::Orientation direction,
|
||||
bool adjustOnResize = false);
|
||||
void addWidget(QWidget *widgetToAdd) const;
|
||||
void insertWidgetAtIndex(QWidget *toInsert, int index);
|
||||
void removeWidget(QWidget *widgetToRemove) const;
|
||||
void clearLayout();
|
||||
void adjustMaxColumnsAndRows();
|
||||
|
||||
public slots:
|
||||
void maxOverlapItemsChanged(int newValue);
|
||||
void overlapDirectionChanged(const QString &newDirection);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
OverlapLayout *overlapLayout;
|
||||
int overlapPercentage;
|
||||
int maxColumns;
|
||||
int maxRows;
|
||||
Qt::Orientation direction;
|
||||
bool adjustOnResize = false;
|
||||
};
|
||||
|
||||
#endif // OVERLAP_WIDGET_H
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
#include "all_zones_card_amount_widget.h"
|
||||
|
||||
#include "../general/display/shadow_background_label.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
/**
|
||||
* @brief Constructor for the AllZonesCardAmountWidget class.
|
||||
*
|
||||
* Initializes the widget with its layout and sets up the connections and necessary
|
||||
* UI elements for managing card counts in both the mainboard and sideboard zones.
|
||||
*
|
||||
* @param parent The parent widget.
|
||||
* @param deckEditor Pointer to the TabDeckEditor.
|
||||
* @param deckModel Pointer to the DeckListModel.
|
||||
* @param deckView Pointer to the QTreeView for the deck display.
|
||||
* @param cardSizeSlider Pointer to the QSlider used for dynamic font resizing.
|
||||
* @param rootCard The root card for the widget.
|
||||
*/
|
||||
AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent,
|
||||
AbstractTabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView,
|
||||
QSlider *cardSizeSlider,
|
||||
const ExactCard &rootCard)
|
||||
: QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider),
|
||||
rootCard(rootCard)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
layout->setAlignment(Qt::AlignHCenter);
|
||||
setLayout(layout);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
setContentsMargins(5, 5, 5, 5); // Padding around the text
|
||||
|
||||
zoneLabelMainboard = new ShadowBackgroundLabel(this, tr("Mainboard"));
|
||||
buttonBoxMainboard =
|
||||
new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_MAIN);
|
||||
zoneLabelSideboard = new ShadowBackgroundLabel(this, tr("Sideboard"));
|
||||
buttonBoxSideboard =
|
||||
new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_SIDE);
|
||||
|
||||
layout->addWidget(zoneLabelMainboard, 0, Qt::AlignHCenter | Qt::AlignBottom);
|
||||
layout->addWidget(buttonBoxMainboard, 0, Qt::AlignHCenter | Qt::AlignTop);
|
||||
layout->addSpacing(25);
|
||||
layout->addWidget(zoneLabelSideboard, 0, Qt::AlignHCenter | Qt::AlignBottom);
|
||||
layout->addWidget(buttonBoxSideboard, 0, Qt::AlignHCenter | Qt::AlignTop);
|
||||
|
||||
connect(cardSizeSlider, &QSlider::valueChanged, this, &AllZonesCardAmountWidget::adjustFontSize);
|
||||
|
||||
QTimer::singleShot(10, this, [this]() { adjustFontSize(this->cardSizeSlider->value()); });
|
||||
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adjusts the font size of the zone labels based on the slider value.
|
||||
*
|
||||
* This method calculates the new font size as a percentage of the original font size
|
||||
* based on the slider value and applies it to the zone label text.
|
||||
*
|
||||
* @param scalePercentage The scale percentage from the slider.
|
||||
*/
|
||||
void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage)
|
||||
{
|
||||
const int minFontSize = 8; // Minimum font size
|
||||
const int maxFontSize = 32; // Maximum font size
|
||||
const int basePercentage = 100; // Scale at 100%
|
||||
|
||||
int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225;
|
||||
newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize);
|
||||
|
||||
// Update the font labels
|
||||
QFont zoneLabelFont = zoneLabelMainboard->font();
|
||||
zoneLabelFont.setPointSize(newFontSize);
|
||||
zoneLabelMainboard->setFont(zoneLabelFont);
|
||||
zoneLabelSideboard->setFont(zoneLabelFont);
|
||||
|
||||
// Repaint the widget (if necessary)
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the card count in the mainboard zone.
|
||||
*
|
||||
* @return The number of cards in the mainboard.
|
||||
*/
|
||||
int AllZonesCardAmountWidget::getMainboardAmount()
|
||||
{
|
||||
return buttonBoxMainboard->countCardsInZone(DECK_ZONE_MAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the card count in the sideboard zone.
|
||||
*
|
||||
* @return The number of cards in the sideboard.
|
||||
*/
|
||||
int AllZonesCardAmountWidget::getSideboardAmount()
|
||||
{
|
||||
return buttonBoxSideboard->countCardsInZone(DECK_ZONE_SIDE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the event when the mouse enters the widget.
|
||||
*
|
||||
* This method is triggered when the mouse enters the widget's area, allowing for updates
|
||||
* or interactions such as UI feedback or layout changes.
|
||||
*
|
||||
* @param event The event information for the mouse entry.
|
||||
*/
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
void AllZonesCardAmountWidget::enterEvent(QEnterEvent *event)
|
||||
#else
|
||||
void AllZonesCardAmountWidget::enterEvent(QEvent *event)
|
||||
#endif
|
||||
{
|
||||
QWidget::enterEvent(event);
|
||||
update();
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef ALL_ZONES_CARD_AMOUNT_WIDGET_H
|
||||
#define ALL_ZONES_CARD_AMOUNT_WIDGET_H
|
||||
#include "../../../deck/deck_list_model.h"
|
||||
#include "../../../deck/deck_loader.h"
|
||||
#include "card_amount_widget.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class AllZonesCardAmountWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AllZonesCardAmountWidget(QWidget *parent,
|
||||
AbstractTabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView,
|
||||
QSlider *cardSizeSlider,
|
||||
const ExactCard &rootCard);
|
||||
int getMainboardAmount();
|
||||
int getSideboardAmount();
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
#else
|
||||
void enterEvent(QEvent *event) override;
|
||||
#endif
|
||||
|
||||
public slots:
|
||||
void adjustFontSize(int scalePercentage);
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QSlider *cardSizeSlider;
|
||||
ExactCard rootCard;
|
||||
QLabel *zoneLabelMainboard;
|
||||
CardAmountWidget *buttonBoxMainboard;
|
||||
QLabel *zoneLabelSideboard;
|
||||
CardAmountWidget *buttonBoxSideboard;
|
||||
};
|
||||
|
||||
#endif // ALL_ZONES_CARD_AMOUNT_WIDGET_H
|
||||
|
|
@ -0,0 +1,300 @@
|
|||
#include "card_amount_widget.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QTimer>
|
||||
|
||||
/**
|
||||
* @brief Constructs a widget for displaying and controlling the card count in a specific zone.
|
||||
*
|
||||
* @param parent The parent widget.
|
||||
* @param deckEditor Pointer to the TabDeckEditor instance.
|
||||
* @param deckModel Pointer to the DeckListModel instance.
|
||||
* @param deckView Pointer to the QTreeView displaying the deck.
|
||||
* @param cardSizeSlider Pointer to the QSlider for adjusting font size.
|
||||
* @param rootCard The root card to manage within the widget.
|
||||
* @param zoneName The zone name (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE).
|
||||
*/
|
||||
CardAmountWidget::CardAmountWidget(QWidget *parent,
|
||||
AbstractTabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView,
|
||||
QSlider *cardSizeSlider,
|
||||
const ExactCard &rootCard,
|
||||
const QString &zoneName)
|
||||
: QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider),
|
||||
rootCard(rootCard), zoneName(zoneName), hovered(false)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(10);
|
||||
this->setLayout(layout);
|
||||
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
layout->setAlignment(Qt::AlignHCenter);
|
||||
|
||||
incrementButton = new DynamicFontSizePushButton(this);
|
||||
incrementButton->setTextAndColor("+", Qt::white);
|
||||
decrementButton = new DynamicFontSizePushButton(this);
|
||||
decrementButton->setTextAndColor("-", Qt::white);
|
||||
|
||||
incrementButton->setFixedSize(parentWidget()->size().width() / 3, parentWidget()->size().height() / 9);
|
||||
decrementButton->setFixedSize(parentWidget()->size().width() / 3, parentWidget()->size().height() / 9);
|
||||
|
||||
// Set up connections based on the zone (Mainboard or Sideboard)
|
||||
if (zoneName == DECK_ZONE_MAIN) {
|
||||
connect(incrementButton, &QPushButton::clicked, this, &CardAmountWidget::addPrintingMainboard);
|
||||
connect(decrementButton, &QPushButton::clicked, this, &CardAmountWidget::removePrintingMainboard);
|
||||
} else if (zoneName == DECK_ZONE_SIDE) {
|
||||
connect(incrementButton, &QPushButton::clicked, this, &CardAmountWidget::addPrintingSideboard);
|
||||
connect(decrementButton, &QPushButton::clicked, this, &CardAmountWidget::removePrintingSideboard);
|
||||
}
|
||||
|
||||
cardCountInZone = new QLabel(QString::number(countCardsInZone(zoneName)), this);
|
||||
cardCountInZone->setAlignment(Qt::AlignCenter);
|
||||
|
||||
layout->addWidget(decrementButton);
|
||||
layout->addWidget(cardCountInZone);
|
||||
layout->addWidget(incrementButton);
|
||||
|
||||
// React to model changes
|
||||
connect(deckModel, &DeckListModel::dataChanged, this, &CardAmountWidget::updateCardCount);
|
||||
connect(deckModel, &QAbstractItemModel::rowsRemoved, this, &CardAmountWidget::updateCardCount);
|
||||
|
||||
// Connect slider for dynamic font size adjustment
|
||||
connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the painting of the widget, drawing a semi-transparent background.
|
||||
*
|
||||
* @param event The paint event.
|
||||
*/
|
||||
void CardAmountWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Draw semi-transparent black background
|
||||
painter.setBrush(QBrush(QColor(0, 0, 0, 128)));
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.drawRect(rect());
|
||||
|
||||
QWidget::paintEvent(event);
|
||||
}
|
||||
|
||||
void CardAmountWidget::showEvent(QShowEvent *event)
|
||||
{
|
||||
QWidget::showEvent(event);
|
||||
|
||||
adjustFontSize(this->cardSizeSlider->value());
|
||||
updateCardCount();
|
||||
|
||||
if (parentWidget()) {
|
||||
int width = parentWidget()->size().width();
|
||||
int height = parentWidget()->size().height();
|
||||
|
||||
incrementButton->setFixedSize(width / 3, height / 9);
|
||||
decrementButton->setFixedSize(width / 3, height / 9);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adjusts the font size of the card count label based on the slider value.
|
||||
*
|
||||
* @param scalePercentage The percentage value from the slider for scaling the font size.
|
||||
*/
|
||||
void CardAmountWidget::adjustFontSize(int scalePercentage)
|
||||
{
|
||||
const int minFontSize = 8; ///< Minimum font size
|
||||
const int maxFontSize = 32; ///< Maximum font size
|
||||
const int basePercentage = 100; ///< Scale at 100%
|
||||
|
||||
int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225;
|
||||
newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize);
|
||||
|
||||
// Update the font for card count label
|
||||
QFont cardCountFont = cardCountInZone->font();
|
||||
cardCountFont.setPointSize(newFontSize);
|
||||
cardCountInZone->setFont(cardCountFont);
|
||||
|
||||
incrementButton->setFixedSize(parentWidget()->size().width() / 3, parentWidget()->size().height() / 9);
|
||||
decrementButton->setFixedSize(parentWidget()->size().width() / 3, parentWidget()->size().height() / 9);
|
||||
|
||||
// Repaint the widget
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the card count display in the widget.
|
||||
*/
|
||||
void CardAmountWidget::updateCardCount()
|
||||
{
|
||||
cardCountInZone->setText("<font color='white'>" + QString::number(countCardsInZone(zoneName)) + "</font>");
|
||||
layout->invalidate();
|
||||
layout->activate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a printing of the card to the specified zone (Mainboard or Sideboard).
|
||||
*
|
||||
* @param zone The zone to add the card to (DECK_ZONE_MAIN or DECK_ZONE_SIDE).
|
||||
*/
|
||||
void CardAmountWidget::addPrinting(const QString &zone)
|
||||
{
|
||||
// Add the card and expand the list UI
|
||||
auto newCardIndex = deckModel->addCard(rootCard, zone);
|
||||
recursiveExpand(newCardIndex);
|
||||
|
||||
// Check if a card without a providerId already exists in the deckModel and replace it, if so.
|
||||
QModelIndex find_card = deckModel->findCard(rootCard.getName(), zone);
|
||||
QString foundProviderId = deckModel->data(find_card.sibling(find_card.row(), 4), Qt::DisplayRole).toString();
|
||||
if (find_card.isValid() && find_card != newCardIndex && foundProviderId == "") {
|
||||
auto amount = deckModel->data(find_card, Qt::DisplayRole);
|
||||
for (int i = 0; i < amount.toInt() - 1; i++) {
|
||||
deckModel->addCard(rootCard, zone);
|
||||
}
|
||||
deckModel->removeRow(find_card.row(), find_card.parent());
|
||||
}
|
||||
|
||||
// Set Index and Focus as if the user had just clicked the new card and modify the deckEditor saveState
|
||||
newCardIndex = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(),
|
||||
rootCard.getPrinting().getProperty("num"));
|
||||
deckView->setCurrentIndex(newCardIndex);
|
||||
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
|
||||
deckEditor->setModified(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a printing to the mainboard zone.
|
||||
*/
|
||||
void CardAmountWidget::addPrintingMainboard()
|
||||
{
|
||||
addPrinting(DECK_ZONE_MAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a printing to the sideboard zone.
|
||||
*/
|
||||
void CardAmountWidget::addPrintingSideboard()
|
||||
{
|
||||
addPrinting(DECK_ZONE_SIDE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes a printing from the mainboard zone.
|
||||
*/
|
||||
void CardAmountWidget::removePrintingMainboard()
|
||||
{
|
||||
decrementCardHelper(DECK_ZONE_MAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes a printing from the sideboard zone.
|
||||
*/
|
||||
void CardAmountWidget::removePrintingSideboard()
|
||||
{
|
||||
decrementCardHelper(DECK_ZONE_SIDE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recursively expands the card in the deck view starting from the given index.
|
||||
*
|
||||
* @param index The model index of the card to expand.
|
||||
*/
|
||||
void CardAmountWidget::recursiveExpand(const QModelIndex &index)
|
||||
{
|
||||
if (index.parent().isValid()) {
|
||||
recursiveExpand(index.parent());
|
||||
}
|
||||
deckView->expand(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Offsets the card count at the specified index by the given amount.
|
||||
*
|
||||
* @param idx The model index of the card.
|
||||
* @param offset The amount to add or subtract from the card count.
|
||||
*/
|
||||
void CardAmountWidget::offsetCountAtIndex(const QModelIndex &idx, int offset)
|
||||
{
|
||||
if (!idx.isValid() || offset == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QModelIndex numberIndex = idx.sibling(idx.row(), 0);
|
||||
const int count = deckModel->data(numberIndex, Qt::EditRole).toInt();
|
||||
const int new_count = count + offset;
|
||||
deckView->setCurrentIndex(numberIndex);
|
||||
if (new_count <= 0) {
|
||||
deckModel->removeRow(idx.row(), idx.parent());
|
||||
} else {
|
||||
deckModel->setData(numberIndex, new_count, Qt::EditRole);
|
||||
}
|
||||
deckEditor->setModified(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper function to decrement the card count for a given zone.
|
||||
*
|
||||
* @param zone The zone from which to remove the card (DECK_ZONE_MAIN or DECK_ZONE_SIDE).
|
||||
*/
|
||||
void CardAmountWidget::decrementCardHelper(const QString &zone)
|
||||
{
|
||||
QModelIndex idx = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(),
|
||||
rootCard.getPrinting().getProperty("num"));
|
||||
offsetCountAtIndex(idx, -1);
|
||||
deckEditor->setModified(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Counts the number of cards in a specific zone (mainboard or sideboard).
|
||||
*
|
||||
* @param deckZone The name of the zone (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE).
|
||||
* @return The number of cards in the zone.
|
||||
*/
|
||||
int CardAmountWidget::countCardsInZone(const QString &deckZone)
|
||||
{
|
||||
if (rootCard.getPrinting().getUuid().isEmpty()) {
|
||||
return 0; // Cards without uuids/providerIds CANNOT match another card, they are undefined for us.
|
||||
}
|
||||
|
||||
if (!deckModel) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
DeckList *decklist = deckModel->getDeckList();
|
||||
if (!decklist) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
InnerDecklistNode *listRoot = decklist->getRoot();
|
||||
if (!listRoot) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (auto *i : *listRoot) {
|
||||
auto *countCurrentZone = dynamic_cast<InnerDecklistNode *>(i);
|
||||
if (!countCurrentZone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (countCurrentZone->getName() != deckZone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto *cardNode : *countCurrentZone) {
|
||||
auto *currentCard = dynamic_cast<DecklistCardNode *>(cardNode);
|
||||
if (!currentCard) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||
if (currentCard->getCardProviderId() == rootCard.getPrinting().getProperty("uuid")) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
#ifndef CARD_AMOUNT_WIDGET_H
|
||||
#define CARD_AMOUNT_WIDGET_H
|
||||
|
||||
#include "../../../card/card_info.h"
|
||||
#include "../../../deck/deck_list_model.h"
|
||||
#include "../../../deck/deck_loader.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../general/display/dynamic_font_size_push_button.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QTreeView>
|
||||
#include <QWidget>
|
||||
|
||||
class CardAmountWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardAmountWidget(QWidget *parent,
|
||||
AbstractTabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView,
|
||||
QSlider *cardSizeSlider,
|
||||
const ExactCard &rootCard,
|
||||
const QString &zoneName);
|
||||
int countCardsInZone(const QString &deckZone);
|
||||
|
||||
public slots:
|
||||
void updateCardCount();
|
||||
void addPrinting(const QString &zone);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private:
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QSlider *cardSizeSlider;
|
||||
ExactCard rootCard;
|
||||
QString zoneName;
|
||||
QHBoxLayout *layout;
|
||||
DynamicFontSizePushButton *incrementButton;
|
||||
DynamicFontSizePushButton *decrementButton;
|
||||
QLabel *cardCountInZone;
|
||||
|
||||
bool hovered;
|
||||
|
||||
void offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||
void decrementCardHelper(const QString &zoneName);
|
||||
void recursiveExpand(const QModelIndex &index);
|
||||
|
||||
private slots:
|
||||
void addPrintingMainboard();
|
||||
void addPrintingSideboard();
|
||||
void removePrintingMainboard();
|
||||
void removePrintingSideboard();
|
||||
void adjustFontSize(int scalePercentage);
|
||||
};
|
||||
|
||||
#endif // CARD_AMOUNT_WIDGET_H
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
#include "printing_selector.h"
|
||||
|
||||
#include "../../../dialogs/dlg_select_set_for_cards.h"
|
||||
#include "../../../picture_loader/picture_loader.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
#include "printing_selector_card_display_widget.h"
|
||||
#include "printing_selector_card_search_widget.h"
|
||||
#include "printing_selector_card_selection_widget.h"
|
||||
#include "printing_selector_card_sorting_widget.h"
|
||||
|
||||
#include <QFrame>
|
||||
#include <QScrollBar>
|
||||
#include <qboxlayout.h>
|
||||
|
||||
/**
|
||||
* @brief Constructs a PrintingSelector widget to display and manage card printings.
|
||||
*
|
||||
* This constructor initializes the PrintingSelector widget, setting up various child widgets
|
||||
* such as sorting tools, search bar, card size options, and navigation controls. It also connects
|
||||
* signals and slots to update the display when the deck model changes, and loads available printings
|
||||
* for the selected card.
|
||||
*
|
||||
* @param parent The parent widget for the PrintingSelector.
|
||||
* @param deckEditor The TabDeckEditor instance used for managing the deck.
|
||||
* @param deckModel The DeckListModel instance that provides data for the deck's contents.
|
||||
* @param deckView The QTreeView instance used to display the deck and its contents.
|
||||
*/
|
||||
PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deckEditor)
|
||||
: QWidget(parent), deckEditor(_deckEditor), deckModel(deckEditor->deckDockWidget->deckModel),
|
||||
deckView(deckEditor->deckDockWidget->deckView)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
widgetLoadingBufferTimer = new QTimer(this);
|
||||
|
||||
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
|
||||
|
||||
sortToolBar = new PrintingSelectorCardSortingWidget(this);
|
||||
sortToolBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
|
||||
displayOptionsWidget = new SettingsButtonWidget(this);
|
||||
displayOptionsWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
|
||||
|
||||
// Create the checkbox for navigation buttons visibility
|
||||
navigationCheckBox = new QCheckBox(this);
|
||||
navigationCheckBox->setChecked(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible());
|
||||
connect(navigationCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
|
||||
&PrintingSelector::toggleVisibilityNavigationButtons);
|
||||
connect(navigationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
|
||||
&SettingsCache::setPrintingSelectorNavigationButtonsVisible);
|
||||
|
||||
cardSizeWidget =
|
||||
new CardSizeWidget(displayOptionsWidget, flowWidget, SettingsCache::instance().getPrintingSelectorCardSize());
|
||||
connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(),
|
||||
&SettingsCache::setPrintingSelectorCardSize);
|
||||
|
||||
displayOptionsWidget->addSettingsWidget(sortToolBar);
|
||||
displayOptionsWidget->addSettingsWidget(navigationCheckBox);
|
||||
displayOptionsWidget->addSettingsWidget(cardSizeWidget);
|
||||
|
||||
sortAndOptionsContainer = new QWidget(this);
|
||||
sortAndOptionsLayout = new QHBoxLayout(sortAndOptionsContainer);
|
||||
sortAndOptionsLayout->setSpacing(3);
|
||||
sortAndOptionsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
sortAndOptionsContainer->setLayout(sortAndOptionsLayout);
|
||||
|
||||
searchBar = new PrintingSelectorCardSearchWidget(this);
|
||||
|
||||
sortAndOptionsLayout->addWidget(searchBar);
|
||||
sortAndOptionsLayout->addWidget(displayOptionsWidget);
|
||||
|
||||
layout->addWidget(sortAndOptionsContainer);
|
||||
|
||||
layout->addWidget(flowWidget);
|
||||
|
||||
cardSelectionBar = new PrintingSelectorCardSelectionWidget(this);
|
||||
cardSelectionBar->setVisible(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible());
|
||||
layout->addWidget(cardSelectionBar);
|
||||
|
||||
// Connect deck model data change signal to update display
|
||||
connect(deckModel, &DeckListModel::rowsInserted, this, &PrintingSelector::printingsInDeckChanged);
|
||||
connect(deckModel, &DeckListModel::rowsRemoved, this, &PrintingSelector::printingsInDeckChanged);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void PrintingSelector::retranslateUi()
|
||||
{
|
||||
navigationCheckBox->setText(tr("Display Navigation Buttons"));
|
||||
}
|
||||
|
||||
void PrintingSelector::printingsInDeckChanged()
|
||||
{
|
||||
// Delay the update to avoid race conditions
|
||||
QTimer::singleShot(100, this, &PrintingSelector::updateDisplay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the display by clearing the layout and loading new sets for the current card.
|
||||
*/
|
||||
void PrintingSelector::updateDisplay()
|
||||
{
|
||||
widgetLoadingBufferTimer->stop();
|
||||
widgetLoadingBufferTimer->deleteLater();
|
||||
widgetLoadingBufferTimer = new QTimer(this);
|
||||
flowWidget->clearLayout();
|
||||
if (selectedCard != nullptr) {
|
||||
setWindowTitle(selectedCard->getName());
|
||||
}
|
||||
getAllSetsForCurrentCard();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the current card for the selector and updates the display.
|
||||
*
|
||||
* @param newCard The new card to set.
|
||||
* @param _currentZone The current zone the card is in.
|
||||
*/
|
||||
void PrintingSelector::setCard(const CardInfoPtr &newCard, const QString &_currentZone)
|
||||
{
|
||||
if (newCard.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we don't need to redraw the widget if the card is the same
|
||||
if (!selectedCard.isNull() && selectedCard->getName() == newCard->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedCard = newCard;
|
||||
currentZone = _currentZone;
|
||||
if (isVisible()) {
|
||||
updateDisplay();
|
||||
}
|
||||
flowWidget->setMinimumSizeToMaxSizeHint();
|
||||
flowWidget->scrollArea->verticalScrollBar()->setValue(0);
|
||||
flowWidget->repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Selects the previous card in the list.
|
||||
*/
|
||||
void PrintingSelector::selectPreviousCard()
|
||||
{
|
||||
selectCard(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Selects the next card in the list.
|
||||
*/
|
||||
void PrintingSelector::selectNextCard()
|
||||
{
|
||||
selectCard(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Selects a card based on the change direction.
|
||||
*
|
||||
* @param changeBy The direction to change, -1 for previous, 1 for next.
|
||||
*/
|
||||
void PrintingSelector::selectCard(const int changeBy)
|
||||
{
|
||||
if (changeBy == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current index of the selected item
|
||||
auto deckViewCurrentIndex = deckView->currentIndex();
|
||||
|
||||
auto nextIndex = deckViewCurrentIndex.siblingAtRow(deckViewCurrentIndex.row() + changeBy);
|
||||
if (!nextIndex.isValid()) {
|
||||
nextIndex = deckViewCurrentIndex;
|
||||
|
||||
// Increment to the next valid index, skipping header rows
|
||||
AbstractDecklistNode *node;
|
||||
do {
|
||||
if (changeBy > 0) {
|
||||
nextIndex = deckView->indexBelow(nextIndex);
|
||||
} else {
|
||||
nextIndex = deckView->indexAbove(nextIndex);
|
||||
}
|
||||
node = static_cast<AbstractDecklistNode *>(nextIndex.internalPointer());
|
||||
} while (node && node->isDeckHeader());
|
||||
}
|
||||
|
||||
if (nextIndex.isValid()) {
|
||||
deckView->setCurrentIndex(nextIndex);
|
||||
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads and displays all sets for the current selected card.
|
||||
*/
|
||||
void PrintingSelector::getAllSetsForCurrentCard()
|
||||
{
|
||||
if (selectedCard.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetToPrintingsMap setMap = selectedCard->getSets();
|
||||
const QList<PrintingInfo> sortedPrintings = sortToolBar->sortSets(setMap);
|
||||
const QList<PrintingInfo> filteredPrintings =
|
||||
sortToolBar->filterSets(sortedPrintings, searchBar->getSearchText().trimmed().toLower());
|
||||
QList<PrintingInfo> printingsToUse;
|
||||
|
||||
if (SettingsCache::instance().getBumpSetsWithCardsInDeckToTop()) {
|
||||
printingsToUse = sortToolBar->prependPrintingsInDeck(filteredPrintings, selectedCard, deckModel);
|
||||
} else {
|
||||
printingsToUse = filteredPrintings;
|
||||
}
|
||||
printingsToUse = sortToolBar->prependPinnedPrintings(printingsToUse, selectedCard->getName());
|
||||
|
||||
// Defer widget creation
|
||||
currentIndex = 0;
|
||||
|
||||
connect(widgetLoadingBufferTimer, &QTimer::timeout, this, [=, this]() mutable {
|
||||
for (int i = 0; i < BATCH_SIZE && currentIndex < printingsToUse.size(); ++i, ++currentIndex) {
|
||||
ExactCard card = ExactCard(selectedCard, printingsToUse[currentIndex]);
|
||||
auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(
|
||||
this, deckEditor, deckModel, deckView, cardSizeWidget->getSlider(), card, currentZone);
|
||||
flowWidget->addWidget(cardDisplayWidget);
|
||||
cardDisplayWidget->clampSetNameToPicture();
|
||||
connect(cardDisplayWidget, &PrintingSelectorCardDisplayWidget::cardPreferenceChanged, this,
|
||||
&PrintingSelector::updateDisplay);
|
||||
}
|
||||
|
||||
// Stop timer when done
|
||||
if (currentIndex >= printingsToUse.size()) {
|
||||
widgetLoadingBufferTimer->stop();
|
||||
}
|
||||
});
|
||||
currentIndex = 0;
|
||||
widgetLoadingBufferTimer->start(0); // Process as soon as possible
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Toggles the visibility of the navigation buttons.
|
||||
*
|
||||
* @param _state The visibility state to set.
|
||||
*/
|
||||
void PrintingSelector::toggleVisibilityNavigationButtons(bool _state)
|
||||
{
|
||||
cardSelectionBar->setVisible(_state);
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef PRINTING_SELECTOR_H
|
||||
#define PRINTING_SELECTOR_H
|
||||
|
||||
#include "../../../card/card_info.h"
|
||||
#include "../../../deck/deck_list_model.h"
|
||||
#include "../cards/card_size_widget.h"
|
||||
#include "../general/layout_containers/flow_widget.h"
|
||||
#include "../quick_settings/settings_button_widget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#define BATCH_SIZE 10
|
||||
|
||||
class PrintingSelectorCardSearchWidget;
|
||||
class PrintingSelectorCardSelectionWidget;
|
||||
class PrintingSelectorCardSortingWidget;
|
||||
class PrintingSelectorViewOptionsWidget;
|
||||
class AbstractTabDeckEditor;
|
||||
class PrintingSelector : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PrintingSelector(QWidget *parent, AbstractTabDeckEditor *deckEditor);
|
||||
|
||||
void setCard(const CardInfoPtr &newCard, const QString &_currentZone);
|
||||
void getAllSetsForCurrentCard();
|
||||
DeckListModel *getDeckModel() const
|
||||
{
|
||||
return deckModel;
|
||||
};
|
||||
|
||||
public slots:
|
||||
void retranslateUi();
|
||||
void updateDisplay();
|
||||
void selectPreviousCard();
|
||||
void selectNextCard();
|
||||
void toggleVisibilityNavigationButtons(bool _state);
|
||||
|
||||
private slots:
|
||||
void printingsInDeckChanged();
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
SettingsButtonWidget *displayOptionsWidget;
|
||||
QWidget *sortAndOptionsContainer;
|
||||
QHBoxLayout *sortAndOptionsLayout;
|
||||
QCheckBox *navigationCheckBox;
|
||||
PrintingSelectorCardSortingWidget *sortToolBar;
|
||||
PrintingSelectorCardSearchWidget *searchBar;
|
||||
FlowWidget *flowWidget;
|
||||
CardSizeWidget *cardSizeWidget;
|
||||
PrintingSelectorCardSelectionWidget *cardSelectionBar;
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
CardInfoPtr selectedCard;
|
||||
QString currentZone;
|
||||
QTimer *widgetLoadingBufferTimer;
|
||||
int currentIndex = 0;
|
||||
void selectCard(int changeBy);
|
||||
};
|
||||
|
||||
#endif // PRINTING_SELECTOR_H
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
#include "printing_selector_card_display_widget.h"
|
||||
|
||||
#include "card_amount_widget.h"
|
||||
#include "printing_selector_card_overlay_widget.h"
|
||||
#include "set_name_and_collectors_number_display_widget.h"
|
||||
|
||||
#include <QGraphicsEffect>
|
||||
#include <QStackedWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* @brief Constructs a PrintingSelectorCardDisplayWidget to display card information.
|
||||
*
|
||||
* This widget is responsible for displaying the selected card's printing information, including
|
||||
* the card's image and set details. It also handles the layout of the card's display, including
|
||||
* its size, set name, and collectors number. The card is displayed within a `QVBoxLayout` with
|
||||
* two main components: the overlay (which combines the card image and buttons) and the set name and collectors number
|
||||
* display.
|
||||
*
|
||||
* @param parent The parent widget for this display.
|
||||
* @param _deckEditor The TabDeckEditor instance for deck management.
|
||||
* @param _deckModel The DeckListModel instance providing deck data.
|
||||
* @param _deckView The QTreeView instance displaying the deck.
|
||||
* @param _cardSizeSlider The slider controlling the size of the displayed card.
|
||||
* @param _rootCard The root card object, representing the card to be displayed.
|
||||
* @param _currentZone The current zone in which the card is located.
|
||||
*/
|
||||
PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *parent,
|
||||
AbstractTabDeckEditor *_deckEditor,
|
||||
DeckListModel *_deckModel,
|
||||
QTreeView *_deckView,
|
||||
QSlider *_cardSizeSlider,
|
||||
const ExactCard &_rootCard,
|
||||
QString &_currentZone)
|
||||
: QWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), deckView(_deckView),
|
||||
cardSizeSlider(_cardSizeSlider), rootCard(_rootCard), currentZone(_currentZone)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
// Create the overlay widget for the card display
|
||||
overlayWidget =
|
||||
new PrintingSelectorCardOverlayWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard);
|
||||
connect(overlayWidget, &PrintingSelectorCardOverlayWidget::cardPreferenceChanged, this,
|
||||
[this]() { emit cardPreferenceChanged(); });
|
||||
|
||||
CardSetPtr set = rootCard.getPrinting().getSet();
|
||||
|
||||
// Create the widget to display the set name and collector's number
|
||||
QString combinedSetName = QString(set->getLongName() + " (" + set->getShortName() + ")");
|
||||
setNameAndCollectorsNumberDisplayWidget = new SetNameAndCollectorsNumberDisplayWidget(
|
||||
this, combinedSetName, rootCard.getPrinting().getProperty("num"), cardSizeSlider);
|
||||
|
||||
// Add the widgets to the layout
|
||||
layout->addWidget(overlayWidget, 0, Qt::AlignHCenter);
|
||||
layout->addWidget(setNameAndCollectorsNumberDisplayWidget, 1, Qt::AlignHCenter | Qt::AlignBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adjusts the width of the set name display to fit the card overlay widget.
|
||||
*
|
||||
* This method ensures that the set name and collector's number display widget does not exceed
|
||||
* the width of the card's overlay widget. It clamps the set name widget to match the width of
|
||||
* the overlay widget and updates the display.
|
||||
*/
|
||||
void PrintingSelectorCardDisplayWidget::clampSetNameToPicture()
|
||||
{
|
||||
if (overlayWidget != nullptr && setNameAndCollectorsNumberDisplayWidget != nullptr) {
|
||||
setNameAndCollectorsNumberDisplayWidget->setMaximumWidth(overlayWidget->width());
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H
|
||||
#define PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../card/card_info.h"
|
||||
#include "../../../deck/deck_list_model.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "printing_selector_card_overlay_widget.h"
|
||||
#include "set_name_and_collectors_number_display_widget.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QWidget>
|
||||
|
||||
class PrintingSelectorCardDisplayWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PrintingSelectorCardDisplayWidget(QWidget *parent,
|
||||
AbstractTabDeckEditor *_deckEditor,
|
||||
DeckListModel *_deckModel,
|
||||
QTreeView *_deckView,
|
||||
QSlider *_cardSizeSlider,
|
||||
const ExactCard &_rootCard,
|
||||
QString &_currentZone);
|
||||
|
||||
public slots:
|
||||
void clampSetNameToPicture();
|
||||
|
||||
signals:
|
||||
void cardPreferenceChanged();
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
SetNameAndCollectorsNumberDisplayWidget *setNameAndCollectorsNumberDisplayWidget;
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QSlider *cardSizeSlider;
|
||||
ExactCard rootCard;
|
||||
QString currentZone;
|
||||
PrintingSelectorCardOverlayWidget *overlayWidget;
|
||||
};
|
||||
|
||||
#endif // PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
#include "printing_selector_card_overlay_widget.h"
|
||||
|
||||
#include "../../../database/card_database_manager.h"
|
||||
#include "../../../settings/cache_settings.h"
|
||||
#include "printing_selector_card_display_widget.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QMouseEvent>
|
||||
#include <QVBoxLayout>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* @brief Constructs a PrintingSelectorCardOverlayWidget for displaying a card overlay.
|
||||
*
|
||||
* This widget is responsible for showing the card's image and providing interactive features such
|
||||
* as a context menu and the ability to adjust the card's scale. It includes the card's image as well
|
||||
* as a widget that displays the card amounts in different zones (mainboard, sideboard, etc.).
|
||||
*
|
||||
* @param parent The parent widget for this overlay.
|
||||
* @param _deckEditor The TabDeckEditor instance for deck management.
|
||||
* @param _deckModel The DeckListModel instance providing deck data.
|
||||
* @param _deckView The QTreeView instance displaying the deck.
|
||||
* @param _cardSizeSlider The slider controlling the size of the card.
|
||||
* @param _rootCard The root card object that contains information about the card.
|
||||
*/
|
||||
PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *parent,
|
||||
AbstractTabDeckEditor *_deckEditor,
|
||||
DeckListModel *_deckModel,
|
||||
QTreeView *_deckView,
|
||||
QSlider *_cardSizeSlider,
|
||||
const ExactCard &_rootCard)
|
||||
: QWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), deckView(_deckView),
|
||||
cardSizeSlider(_cardSizeSlider), rootCard(_rootCard)
|
||||
{
|
||||
// Set up the main layout
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->setSpacing(0);
|
||||
setLayout(mainLayout);
|
||||
|
||||
// Add CardInfoPictureWidget
|
||||
cardInfoPicture = new CardInfoPictureWidget(this);
|
||||
cardInfoPicture->setMinimumSize(0, 0);
|
||||
cardInfoPicture->setScaleFactor(cardSizeSlider->value());
|
||||
cardInfoPicture->setCard(_rootCard);
|
||||
mainLayout->addWidget(cardInfoPicture);
|
||||
|
||||
// Add AllZonesCardAmountWidget
|
||||
allZonesCardAmountWidget =
|
||||
new AllZonesCardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, _rootCard);
|
||||
|
||||
allZonesCardAmountWidget->raise(); // Ensure it's on top of the picture
|
||||
// Set initial visibility based on amounts
|
||||
if (allZonesCardAmountWidget->getMainboardAmount() > 0 || allZonesCardAmountWidget->getSideboardAmount() > 0) {
|
||||
allZonesCardAmountWidget->setVisible(true);
|
||||
} else {
|
||||
allZonesCardAmountWidget->setVisible(false);
|
||||
}
|
||||
|
||||
// Attempt to cast the parent to PrintingSelectorCardDisplayWidget
|
||||
if (const auto *parentWidget = qobject_cast<PrintingSelectorCardDisplayWidget *>(parent)) {
|
||||
connect(cardInfoPicture, &CardInfoPictureWidget::cardScaleFactorChanged, parentWidget,
|
||||
&PrintingSelectorCardDisplayWidget::clampSetNameToPicture);
|
||||
}
|
||||
|
||||
connect(cardSizeSlider, &QSlider::valueChanged, cardInfoPicture, &CardInfoPictureWidget::setScaleFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the mouse press event for right-clicks to show the context menu.
|
||||
*
|
||||
* If the right mouse button is pressed, a custom context menu will appear. For other mouse buttons,
|
||||
* the event is passed to the base class for default handling.
|
||||
*
|
||||
* @param event The mouse event triggered by the user.
|
||||
*/
|
||||
void PrintingSelectorCardOverlayWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::RightButton) {
|
||||
customMenu(event->pos());
|
||||
} else {
|
||||
QWidget::mousePressEvent(event); // Pass other events to the base class
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resizes the overlay widget to match the card's size.
|
||||
*
|
||||
* This method ensures that the amount widget matches the card's size when the overlay widget is resized.
|
||||
* It also resizes the card info picture widget to match the new size.
|
||||
*
|
||||
* @param event The resize event triggered when the widget is resized.
|
||||
*/
|
||||
void PrintingSelectorCardOverlayWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
// Ensure the amount widget matches the parent size
|
||||
QWidget::resizeEvent(event);
|
||||
if (allZonesCardAmountWidget) {
|
||||
allZonesCardAmountWidget->resize(cardInfoPicture->size());
|
||||
}
|
||||
resize(cardInfoPicture->size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the mouse enter event when the cursor enters the overlay widget area.
|
||||
*
|
||||
* When the cursor enters the widget, the card information is updated, and the card amount widget
|
||||
* is displayed if the amounts are zero for both the mainboard and sideboard.
|
||||
*
|
||||
* @param event The event triggered when the mouse enters the widget.
|
||||
*/
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void PrintingSelectorCardOverlayWidget::enterEvent(QEnterEvent *event)
|
||||
#else
|
||||
void PrintingSelectorCardOverlayWidget::enterEvent(QEvent *event)
|
||||
#endif
|
||||
{
|
||||
QWidget::enterEvent(event);
|
||||
deckEditor->updateCard(rootCard);
|
||||
|
||||
// Check if either mainboard or sideboard amount is greater than 0
|
||||
if (allZonesCardAmountWidget->getMainboardAmount() > 0 || allZonesCardAmountWidget->getSideboardAmount() > 0) {
|
||||
// Don't change visibility if amounts are greater than 0
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the widget if amounts are 0
|
||||
allZonesCardAmountWidget->setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the mouse leave event when the cursor leaves the overlay widget area.
|
||||
*
|
||||
* When the cursor leaves the widget, the card amount widget is hidden if both the mainboard and sideboard
|
||||
* amounts are zero.
|
||||
*
|
||||
* @param event The event triggered when the mouse leaves the widget.
|
||||
*/
|
||||
void PrintingSelectorCardOverlayWidget::leaveEvent(QEvent *event)
|
||||
{
|
||||
QWidget::leaveEvent(event);
|
||||
|
||||
// Check if either mainboard or sideboard amount is greater than 0
|
||||
if (allZonesCardAmountWidget->getMainboardAmount() > 0 || allZonesCardAmountWidget->getSideboardAmount() > 0) {
|
||||
// Don't hide the widget if amounts are greater than 0
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide the widget if amounts are 0
|
||||
allZonesCardAmountWidget->setVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates and shows a custom context menu when the right mouse button is clicked.
|
||||
*
|
||||
* The context menu includes an option to show related cards, which displays a submenu with actions
|
||||
* for each related card. When an action is triggered, the card information is updated, and the
|
||||
* printing selector is shown.
|
||||
*
|
||||
* @param point The position of the mouse when the right-click occurred.
|
||||
*/
|
||||
void PrintingSelectorCardOverlayWidget::customMenu(QPoint point)
|
||||
{
|
||||
QMenu menu;
|
||||
|
||||
auto *preferenceMenu = new QMenu(tr("Preference"));
|
||||
menu.addMenu(preferenceMenu);
|
||||
|
||||
const auto &preferredProviderId =
|
||||
SettingsCache::instance().cardOverrides().getCardPreferenceOverride(rootCard.getName());
|
||||
const auto &cardProviderId = rootCard.getPrinting().getUuid();
|
||||
|
||||
if (preferredProviderId.isEmpty() || preferredProviderId != cardProviderId) {
|
||||
auto *pinAction = preferenceMenu->addAction(tr("Pin Printing"));
|
||||
connect(pinAction, &QAction::triggered, this, [this] {
|
||||
SettingsCache::instance().cardOverrides().setCardPreferenceOverride(
|
||||
{rootCard.getName(), rootCard.getPrinting().getUuid()});
|
||||
emit cardPreferenceChanged();
|
||||
});
|
||||
} else {
|
||||
auto *unpinAction = preferenceMenu->addAction(tr("Unpin Printing"));
|
||||
connect(unpinAction, &QAction::triggered, this, [this] {
|
||||
SettingsCache::instance().cardOverrides().deleteCardPreferenceOverride(rootCard.getName());
|
||||
emit cardPreferenceChanged();
|
||||
});
|
||||
}
|
||||
|
||||
// filling out the related cards submenu
|
||||
auto *relatedMenu = new QMenu(tr("Show Related cards"));
|
||||
menu.addMenu(relatedMenu);
|
||||
auto relatedCards = rootCard.getInfo().getAllRelatedCards();
|
||||
if (relatedCards.isEmpty()) {
|
||||
relatedMenu->setDisabled(true);
|
||||
} else {
|
||||
for (const CardRelation *rel : relatedCards) {
|
||||
const QString &relatedCardName = rel->getName();
|
||||
QAction *relatedCard = relatedMenu->addAction(relatedCardName);
|
||||
connect(relatedCard, &QAction::triggered, deckEditor, [this, relatedCardName] {
|
||||
deckEditor->updateCard(CardDatabaseManager::getInstance()->getCard({relatedCardName}));
|
||||
deckEditor->showPrintingSelector();
|
||||
});
|
||||
}
|
||||
}
|
||||
menu.exec(this->mapToGlobal(point));
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H
|
||||
#define PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H
|
||||
|
||||
#include "../../../card/card_info.h"
|
||||
#include "../../../deck/deck_list_model.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../cards/card_info_picture_widget.h"
|
||||
#include "all_zones_card_amount_widget.h"
|
||||
#include "card_amount_widget.h"
|
||||
#include "set_name_and_collectors_number_display_widget.h"
|
||||
|
||||
class PrintingSelectorCardOverlayWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PrintingSelectorCardOverlayWidget(QWidget *parent,
|
||||
AbstractTabDeckEditor *_deckEditor,
|
||||
DeckListModel *_deckModel,
|
||||
QTreeView *_deckView,
|
||||
QSlider *_cardSizeSlider,
|
||||
const ExactCard &_rootCard);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
#else
|
||||
void enterEvent(QEvent *event) override;
|
||||
#endif
|
||||
void leaveEvent(QEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void customMenu(QPoint point);
|
||||
|
||||
signals:
|
||||
void cardPreferenceChanged();
|
||||
|
||||
private:
|
||||
CardInfoPictureWidget *cardInfoPicture;
|
||||
AllZonesCardAmountWidget *allZonesCardAmountWidget;
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QSlider *cardSizeSlider;
|
||||
ExactCard rootCard;
|
||||
};
|
||||
|
||||
#endif // PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#include "printing_selector_card_search_widget.h"
|
||||
|
||||
/**
|
||||
* @brief Constructs a PrintingSelectorCardSearchWidget for searching cards by set name or set code.
|
||||
*
|
||||
* This widget provides a search bar that allows users to search for cards by either their set name
|
||||
* or set code. It uses a debounced timer to trigger the search action after the user stops typing.
|
||||
*
|
||||
* @param parent The parent PrintingSelector widget that will handle the search results.
|
||||
*/
|
||||
PrintingSelectorCardSearchWidget::PrintingSelectorCardSearchWidget(PrintingSelector *parent) : parent(parent)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(9, 0, 9, 0);
|
||||
setLayout(layout);
|
||||
|
||||
searchBar = new QLineEdit(this);
|
||||
searchBar->setPlaceholderText(tr("Search by set name or set code"));
|
||||
layout->addWidget(searchBar);
|
||||
|
||||
// Add a debounce timer for the search bar to limit frequent updates
|
||||
searchDebounceTimer = new QTimer(this);
|
||||
searchDebounceTimer->setSingleShot(true);
|
||||
connect(searchBar, &QLineEdit::textChanged, this, [this]() {
|
||||
searchDebounceTimer->start(300); // 300ms debounce
|
||||
});
|
||||
|
||||
connect(searchDebounceTimer, &QTimer::timeout, parent, &PrintingSelector::updateDisplay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves the current text in the search bar.
|
||||
*
|
||||
* @return The text entered by the user in the search bar.
|
||||
*/
|
||||
QString PrintingSelectorCardSearchWidget::getSearchText()
|
||||
{
|
||||
return searchBar->text();
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef PRINTING_SELECTOR_CARD_SEARCH_WIDGET_H
|
||||
#define PRINTING_SELECTOR_CARD_SEARCH_WIDGET_H
|
||||
|
||||
#include "printing_selector.h"
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
class PrintingSelectorCardSearchWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PrintingSelectorCardSearchWidget(PrintingSelector *parent);
|
||||
QString getSearchText();
|
||||
|
||||
private:
|
||||
QHBoxLayout *layout;
|
||||
PrintingSelector *parent;
|
||||
QLineEdit *searchBar;
|
||||
QTimer *searchDebounceTimer;
|
||||
};
|
||||
|
||||
#endif // PRINTING_SELECTOR_CARD_SEARCH_WIDGET_H
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#include "printing_selector_card_selection_widget.h"
|
||||
|
||||
#include "../../../dialogs/dlg_select_set_for_cards.h"
|
||||
|
||||
/**
|
||||
* @brief Constructs a PrintingSelectorCardSelectionWidget for navigating through cards in the deck.
|
||||
*
|
||||
* This widget provides buttons that allow users to navigate between cards in the deck.
|
||||
* It includes buttons for moving to the previous and next card in the deck.
|
||||
*
|
||||
* @param parent The parent PrintingSelector widget responsible for managing card selection.
|
||||
*/
|
||||
PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(PrintingSelector *parent) : parent(parent)
|
||||
{
|
||||
cardSelectionBarLayout = new QHBoxLayout(this);
|
||||
cardSelectionBarLayout->setContentsMargins(9, 0, 9, 0);
|
||||
|
||||
previousCardButton = new QPushButton(this);
|
||||
previousCardButton->setText(tr("Previous Card in Deck"));
|
||||
|
||||
selectSetForCardsButton = new QPushButton(this);
|
||||
connect(selectSetForCardsButton, &QPushButton::clicked, this,
|
||||
&PrintingSelectorCardSelectionWidget::selectSetForCards);
|
||||
selectSetForCardsButton->setText(tr("Bulk Selection"));
|
||||
|
||||
nextCardButton = new QPushButton(this);
|
||||
nextCardButton->setText(tr("Next Card in Deck"));
|
||||
|
||||
connectSignals();
|
||||
|
||||
cardSelectionBarLayout->addWidget(previousCardButton);
|
||||
cardSelectionBarLayout->addWidget(selectSetForCardsButton);
|
||||
cardSelectionBarLayout->addWidget(nextCardButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects the signals from the buttons to the appropriate slots in the parent widget.
|
||||
*
|
||||
* This method connects the click signals of the previous and next card buttons to
|
||||
* the selectPreviousCard and selectNextCard slots in the parent PrintingSelector widget.
|
||||
*/
|
||||
void PrintingSelectorCardSelectionWidget::connectSignals()
|
||||
{
|
||||
connect(previousCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectPreviousCard);
|
||||
connect(nextCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectNextCard);
|
||||
}
|
||||
|
||||
void PrintingSelectorCardSelectionWidget::selectSetForCards()
|
||||
{
|
||||
DlgSelectSetForCards *setSelectionDialog = new DlgSelectSetForCards(nullptr, parent->getDeckModel());
|
||||
if (!setSelectionDialog->exec()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
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