mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-27 08:24:39 -07:00
* [Card] Add a setting for the language used in card search Localized card names and texts can now be searched too, controlled by a 'Language used in card search' toggle (English, selected card language, or both) on the general settings page. Untranslated cards always keep matching in English. Removed the redundant local copy of the URL templates list in the localized picture loader while here. * [Card] Bind search language per FilterString instance The peg parser rules are set up once per process, so the GenericQuery and OracleQuery rule actions could not capture per-instance state. Instead of storing the search language in a process-global that FilterString instance methods mutate, hand it to the rule actions through a thread-local parse context and copy it into the filter closures they produce. Card evaluation in FilterString::check no longer reads any process-global state, and each instance keeps the language it was built with; constructing one instance no longer changes what unrelated instances (deck filter, drop-to-hand, zone views) match against. The card database display model stores the raw query and rebuilds the FilterString when the search language changes, since the language is now bound at parse time. Add tests for the English/Selected/Both search modes, the English fallback for untranslated cards, and per-instance language independence. * [Card] Pass the card search language to deck and zone card searches Wire the two remaining FilterString consumers to the configured card search language so card-name matches respect it everywhere: - DeckFilterString now takes the search language and mode, exposes them to its [[card name]] rule action via a thread-local parse context (same pattern as FilterString), and the engine's card database uses them for content search. - ZoneViewZone reads the card language from CardsDisplaySettings when applying its search filter, and the reveal-zone widget re-applies the active search when the language setting changes. - The deck-storage search re-runs its filter against the current card language setting, including live re-application when the setting changes. Game-action targeting (DlgMoveTopCardsUntil) intentionally keeps evaluating against English card names. * [Card] Rename CardSearchLanguage to SearchLanguageMode * [Card] Restore displaced namespace doc in card_localization.h * [Filters] Pass CardSearchLanguage as a single struct * [CardSearchModel] Match English and localized names in Both mode Card names are stored in both English and localized forms, so search for matches in both during the 'Both' search mode instead of checking only the localized name. * [CreateTokenDialog] Fetch cardsDisplay settings inside the apply lambda Avoid capturing the raw settings pointer in the lambda: resolve the card language and card search language from the settings cache at call time so the values are always current when the search language is re-applied. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
597 lines
22 KiB
C++
597 lines
22 KiB
C++
#include "view_zone_widget.h"
|
|
|
|
#include "../../client/settings/cache_settings.h"
|
|
#include "../../filters/syntax_help.h"
|
|
#include "../../game/player/player_actions.h"
|
|
#include "../../game/player/player_logic.h"
|
|
#include "../../interface/pixel_map_generator.h"
|
|
#include "../board/card_item.h"
|
|
#include "../game_scene.h"
|
|
#include "../z_values.h"
|
|
#include "view_zone.h"
|
|
|
|
#include <QCheckBox>
|
|
#include <QGraphicsLinearLayout>
|
|
#include <QGraphicsProxyWidget>
|
|
#include <QGraphicsSceneMouseEvent>
|
|
#include <QGraphicsView>
|
|
#include <QLabel>
|
|
#include <QPainter>
|
|
#include <QScrollBar>
|
|
#include <QStyle>
|
|
#include <QStyleOption>
|
|
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
|
#include <libcockatrice/settings/cards_display_settings.h>
|
|
#include <libcockatrice/settings/interface_settings.h>
|
|
|
|
namespace
|
|
{
|
|
constexpr qreal kTitleBarHeight = 24.0;
|
|
constexpr qreal kMinVisibleWidth = 100.0;
|
|
} // namespace
|
|
|
|
/**
|
|
* @param _player the player the cards were revealed to.
|
|
* @param _origZone the zone the cards were revealed from.
|
|
* @param numberCards num of cards to reveal from the zone. Ex: scry the top 3 cards.
|
|
* Pass in a negative number to reveal the entire zone.
|
|
* -1 specifically will give the option to shuffle the zone upon closing the window.
|
|
* @param _revealZone if false, the cards will be face down.
|
|
* @param _writeableRevealZone whether the player can interact with the revealed cards.
|
|
*/
|
|
ZoneViewWidget::ZoneViewWidget(PlayerLogic *_player,
|
|
CardZoneLogic *_origZone,
|
|
int numberCards,
|
|
bool _revealZone,
|
|
bool _writeableRevealZone,
|
|
const QList<const ServerInfo_Card *> &cardList,
|
|
bool _isReversed)
|
|
: QGraphicsWidget(0, Qt::Window), canBeShuffled(_origZone->getIsShufflable()), player(_player)
|
|
{
|
|
setAcceptHoverEvents(true);
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
setZValue(ZValues::ZONE_VIEW_WIDGET);
|
|
setFlag(ItemIgnoresTransformations);
|
|
|
|
QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical);
|
|
vbox->setSpacing(2);
|
|
|
|
// If the number is < 0, then it means that we can give the option to make the area sorted
|
|
if (numberCards < 0) {
|
|
// search edit
|
|
searchEdit.setFocusPolicy(Qt::ClickFocus);
|
|
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(themePixmap(QStringLiteral("icons/info")), QLineEdit::TrailingPosition);
|
|
|
|
connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(&searchEdit); });
|
|
|
|
if (SettingsCache::instance().userInterface().getFocusCardViewSearchBar()) {
|
|
this->setActive(true);
|
|
searchEdit.setFocus();
|
|
}
|
|
|
|
QGraphicsProxyWidget *searchEditProxy = new QGraphicsProxyWidget;
|
|
searchEditProxy->setWidget(&searchEdit);
|
|
searchEditProxy->setZValue(ZValues::DRAG_ITEM);
|
|
vbox->addItem(searchEditProxy);
|
|
|
|
// hide search bar if chat autofocus setting is enabled, since typing into it will no longer work anyway
|
|
searchEditProxy->setVisible(!SettingsCache::instance().userInterface().getKeepGameChatFocus());
|
|
connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::keepGameChatFocusChanged,
|
|
searchEditProxy, [searchEditProxy](bool keepFocus) { searchEditProxy->setVisible(!keepFocus); });
|
|
|
|
// top row
|
|
QGraphicsLinearLayout *hTopRow = new QGraphicsLinearLayout(Qt::Horizontal);
|
|
|
|
// groupBy options
|
|
QGraphicsProxyWidget *groupBySelectorProxy = new QGraphicsProxyWidget;
|
|
groupBySelectorProxy->setWidget(&groupBySelector);
|
|
groupBySelectorProxy->setZValue(ZValues::TOP_UI);
|
|
hTopRow->addItem(groupBySelectorProxy);
|
|
|
|
// sortBy options
|
|
QGraphicsProxyWidget *sortBySelectorProxy = new QGraphicsProxyWidget;
|
|
sortBySelectorProxy->setWidget(&sortBySelector);
|
|
sortBySelectorProxy->setZValue(ZValues::DRAG_ITEM);
|
|
hTopRow->addItem(sortBySelectorProxy);
|
|
|
|
vbox->addItem(hTopRow);
|
|
|
|
// line
|
|
QGraphicsProxyWidget *lineProxy = new QGraphicsProxyWidget;
|
|
QFrame *line = new QFrame;
|
|
line->setFrameShape(QFrame::HLine);
|
|
line->setFrameShadow(QFrame::Sunken);
|
|
lineProxy->setWidget(line);
|
|
vbox->addItem(lineProxy);
|
|
|
|
// bottom row
|
|
QGraphicsLinearLayout *hBottomRow = new QGraphicsLinearLayout(Qt::Horizontal);
|
|
|
|
// pile view options
|
|
QGraphicsProxyWidget *pileViewProxy = new QGraphicsProxyWidget;
|
|
pileViewProxy->setWidget(&pileViewCheckBox);
|
|
hBottomRow->addItem(pileViewProxy);
|
|
|
|
// shuffle options
|
|
if (_origZone->getIsShufflable() && numberCards == -1) {
|
|
shuffleCheckBox.setChecked(true);
|
|
QGraphicsProxyWidget *shuffleProxy = new QGraphicsProxyWidget;
|
|
shuffleProxy->setWidget(&shuffleCheckBox);
|
|
hBottomRow->addItem(shuffleProxy);
|
|
}
|
|
|
|
vbox->addItem(hBottomRow);
|
|
}
|
|
|
|
extraHeight = vbox->sizeHint(Qt::PreferredSize).height();
|
|
|
|
QGraphicsLinearLayout *zoneHBox = new QGraphicsLinearLayout(Qt::Horizontal);
|
|
|
|
zoneContainer = new QGraphicsWidget(this);
|
|
zoneContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
zoneContainer->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
|
|
zoneHBox->addItem(zoneContainer);
|
|
|
|
scrollBar = new QScrollBar(Qt::Vertical);
|
|
scrollBar->setMinimum(0);
|
|
scrollBar->setSingleStep(20);
|
|
scrollBar->setPageStep(200);
|
|
connect(scrollBar, &QScrollBar::valueChanged, this, &ZoneViewWidget::handleScrollBarChange);
|
|
scrollBarProxy = new ScrollableGraphicsProxyWidget;
|
|
scrollBarProxy->setWidget(scrollBar);
|
|
zoneHBox->addItem(scrollBarProxy);
|
|
|
|
vbox->addItem(zoneHBox);
|
|
|
|
zone = new ZoneViewZone(new ZoneViewZoneLogic(player, _origZone, numberCards, _revealZone, _writeableRevealZone,
|
|
_isReversed, zoneContainer),
|
|
zoneContainer);
|
|
connect(zone, &ZoneViewZone::wheelEventReceived, scrollBarProxy, &ScrollableGraphicsProxyWidget::recieveWheelEvent);
|
|
|
|
retranslateUi();
|
|
|
|
// only wire up sort options after creating ZoneViewZone, since it segfaults otherwise.
|
|
if (numberCards < 0) {
|
|
connect(&groupBySelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
|
&ZoneViewWidget::processGroupBy);
|
|
connect(&sortBySelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
|
&ZoneViewWidget::processSortBy);
|
|
connect(&pileViewCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &ZoneViewWidget::processSetPileView);
|
|
groupBySelector.setCurrentIndex(SettingsCache::instance().userInterface().getZoneViewGroupByIndex());
|
|
sortBySelector.setCurrentIndex(SettingsCache::instance().userInterface().getZoneViewSortByIndex());
|
|
pileViewCheckBox.setChecked(SettingsCache::instance().userInterface().getZoneViewPileView());
|
|
|
|
if (CardList::NoSort == static_cast<CardList::SortOption>(groupBySelector.currentData().toInt())) {
|
|
pileViewCheckBox.setEnabled(false);
|
|
}
|
|
|
|
connect(&searchEdit, &QLineEdit::textChanged, zone, &ZoneViewZone::setFilterString);
|
|
|
|
const auto applyCardSearchLanguage = [this] { zone->setFilterString(searchEdit.text()); };
|
|
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this,
|
|
applyCardSearchLanguage);
|
|
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardSearchLanguageChanged, this,
|
|
applyCardSearchLanguage);
|
|
}
|
|
|
|
setLayout(vbox);
|
|
|
|
connect(zone, &ZoneViewZone::optimumRectChanged, this, [this] { resizeToZoneContents(); });
|
|
connect(zone, &ZoneViewZone::closed, this, &ZoneViewWidget::zoneDeleted);
|
|
zone->initializeCards(cardList);
|
|
|
|
// QLabel sizes aren't taken into account until the widget is rendered.
|
|
// Force refresh after 1ms to fix glitchy rendering with long QLabels.
|
|
auto *lastResizeBeforeVisibleTimer = new QTimer(this);
|
|
connect(lastResizeBeforeVisibleTimer, &QTimer::timeout, this, [=, this] {
|
|
resizeToZoneContents();
|
|
disconnect(lastResizeBeforeVisibleTimer);
|
|
lastResizeBeforeVisibleTimer->deleteLater();
|
|
});
|
|
lastResizeBeforeVisibleTimer->setSingleShot(true);
|
|
lastResizeBeforeVisibleTimer->start(1);
|
|
}
|
|
|
|
void ZoneViewWidget::processGroupBy(int index)
|
|
{
|
|
auto option = static_cast<CardList::SortOption>(groupBySelector.itemData(index).toInt());
|
|
SettingsCache::instance().userInterface().setZoneViewGroupByIndex(index);
|
|
zone->setGroupBy(option);
|
|
|
|
// disable pile view checkbox if we're not grouping by anything
|
|
pileViewCheckBox.setEnabled(option != CardList::NoSort);
|
|
|
|
// reset sortBy if it has the same value as groupBy
|
|
if (option != CardList::NoSort &&
|
|
option == static_cast<CardList::SortOption>(sortBySelector.currentData().toInt())) {
|
|
sortBySelector.setCurrentIndex(1); // set to SortByName
|
|
}
|
|
}
|
|
|
|
void ZoneViewWidget::processSortBy(int index)
|
|
{
|
|
auto option = static_cast<CardList::SortOption>(sortBySelector.itemData(index).toInt());
|
|
|
|
// set to SortByName instead if it has the same value as groupBy
|
|
if (option != CardList::NoSort &&
|
|
option == static_cast<CardList::SortOption>(groupBySelector.currentData().toInt())) {
|
|
sortBySelector.setCurrentIndex(1); // set to SortByName
|
|
return;
|
|
}
|
|
|
|
SettingsCache::instance().userInterface().setZoneViewSortByIndex(index);
|
|
zone->setSortBy(option);
|
|
}
|
|
|
|
void ZoneViewWidget::processSetPileView(QT_STATE_CHANGED_T value)
|
|
{
|
|
SettingsCache::instance().userInterface().setZoneViewPileView(value);
|
|
zone->setPileView(value);
|
|
}
|
|
|
|
void ZoneViewWidget::retranslateUi()
|
|
{
|
|
setWindowTitle(zone->getLogic()->getTranslatedName(false, CaseNominative));
|
|
|
|
{ // We can't change the strings after they're put into the QComboBox, so this is our workaround
|
|
int oldIndex = groupBySelector.currentIndex();
|
|
groupBySelector.clear();
|
|
groupBySelector.addItem(tr("Ungrouped"), CardList::NoSort);
|
|
groupBySelector.addItem(tr("Group by Type"), CardList::SortByMainType);
|
|
groupBySelector.addItem(tr("Group by Mana Value"), CardList::SortByManaValue);
|
|
groupBySelector.addItem(tr("Group by Color"), CardList::SortByColorGrouping);
|
|
groupBySelector.setCurrentIndex(oldIndex);
|
|
}
|
|
|
|
{
|
|
int oldIndex = sortBySelector.currentIndex();
|
|
sortBySelector.clear();
|
|
sortBySelector.addItem(tr("Unsorted"), CardList::NoSort);
|
|
sortBySelector.addItem(tr("Sort by Name"), CardList::SortByName);
|
|
sortBySelector.addItem(tr("Sort by Type"), CardList::SortByType);
|
|
sortBySelector.addItem(tr("Sort by Mana Cost"), CardList::SortByManaCost);
|
|
sortBySelector.addItem(tr("Sort by Colors"), CardList::SortByColors);
|
|
sortBySelector.addItem(tr("Sort by P/T"), CardList::SortByPt);
|
|
sortBySelector.addItem(tr("Sort by Set"), CardList::SortBySet);
|
|
sortBySelector.setCurrentIndex(oldIndex);
|
|
}
|
|
|
|
shuffleCheckBox.setText(tr("shuffle when closing"));
|
|
pileViewCheckBox.setText(tr("pile view"));
|
|
}
|
|
|
|
void ZoneViewWidget::stopWindowDrag()
|
|
{
|
|
if (!draggingWindow) {
|
|
return;
|
|
}
|
|
|
|
draggingWindow = false;
|
|
ungrabMouse();
|
|
}
|
|
|
|
void ZoneViewWidget::startWindowDrag(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
draggingWindow = true;
|
|
dragStartItemPos = pos();
|
|
dragStartScreenPos = event->screenPos();
|
|
dragView = findDragView(event->widget());
|
|
|
|
// need to grab mouse to receive events and not miss initial movement
|
|
grabMouse();
|
|
}
|
|
|
|
QRectF ZoneViewWidget::closeButtonRect(QWidget *styleWidget) const
|
|
{
|
|
const QRectF frameRectF = windowFrameRect();
|
|
|
|
// query the style for the close button position (handles macOS top-left placement)
|
|
// Title bar rect MUST be local (0,0-based) for QStyle
|
|
const QRect titleBarRect(0, 0, static_cast<int>(frameRectF.width()), static_cast<int>(kTitleBarHeight));
|
|
|
|
if (styleWidget) {
|
|
QStyleOptionTitleBar opt;
|
|
opt.initFrom(styleWidget);
|
|
opt.rect = titleBarRect;
|
|
opt.text = windowTitle();
|
|
opt.icon = styleWidget->windowIcon();
|
|
opt.titleBarFlags = Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
|
|
|
|
opt.subControls = QStyle::SC_TitleBarCloseButton;
|
|
opt.activeSubControls = QStyle::SC_TitleBarCloseButton;
|
|
opt.titleBarState = styleWidget->isActiveWindow() ? Qt::WindowActive : Qt::WindowNoState;
|
|
|
|
if (styleWidget->isActiveWindow()) {
|
|
opt.state |= QStyle::State_Active;
|
|
}
|
|
|
|
QRect r = styleWidget->style()->subControlRect(QStyle::CC_TitleBar, &opt, QStyle::SC_TitleBarCloseButton,
|
|
styleWidget);
|
|
|
|
if (r.isValid() && !r.isEmpty()) {
|
|
// Translate from local-titlebar coords → frame coords
|
|
r.translate(frameRectF.topLeft().toPoint());
|
|
return QRectF(r);
|
|
}
|
|
}
|
|
|
|
// Fallback: frame-relative top-right
|
|
return QRectF(frameRectF.right() - kTitleBarHeight, frameRectF.top(), kTitleBarHeight, kTitleBarHeight);
|
|
}
|
|
|
|
QGraphicsView *ZoneViewWidget::findDragView(QWidget *eventWidget) const
|
|
{
|
|
QWidget *current = eventWidget;
|
|
while (current) {
|
|
if (auto *view = qobject_cast<QGraphicsView *>(current)) {
|
|
return view;
|
|
}
|
|
current = current->parentWidget();
|
|
}
|
|
|
|
if (scene() && !scene()->views().isEmpty()) {
|
|
return scene()->views().constFirst();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
QPointF ZoneViewWidget::calcDraggedWindowPos(const QPoint &screenPos,
|
|
const QPointF &scenePos,
|
|
const QPointF &buttonDownScenePos) const
|
|
{
|
|
if (dragView && dragView->viewport()) {
|
|
const QPoint vpStart = dragView->viewport()->mapFromGlobal(dragStartScreenPos);
|
|
const QPoint vpNow = dragView->viewport()->mapFromGlobal(screenPos);
|
|
const QPointF sceneStart = dragView->mapToScene(vpStart);
|
|
const QPointF sceneNow = dragView->mapToScene(vpNow);
|
|
return dragStartItemPos + (sceneNow - sceneStart);
|
|
}
|
|
|
|
return dragStartItemPos + (scenePos - buttonDownScenePos);
|
|
}
|
|
|
|
bool ZoneViewWidget::windowFrameEvent(QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::UngrabMouse) {
|
|
stopWindowDrag();
|
|
return QGraphicsWidget::windowFrameEvent(event);
|
|
}
|
|
|
|
auto *me = dynamic_cast<QGraphicsSceneMouseEvent *>(event);
|
|
if (!me) {
|
|
return QGraphicsWidget::windowFrameEvent(event);
|
|
}
|
|
|
|
switch (event->type()) {
|
|
case QEvent::GraphicsSceneMousePress:
|
|
if (me->button() == Qt::LeftButton && windowFrameSectionAt(me->pos()) == Qt::TitleBarArea) {
|
|
// avoid drag on close button
|
|
if (closeButtonRect(me->widget()).contains(me->pos())) {
|
|
me->accept();
|
|
close();
|
|
return true;
|
|
}
|
|
|
|
startWindowDrag(me);
|
|
me->accept();
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case QEvent::GraphicsSceneMouseMove:
|
|
if (draggingWindow) {
|
|
if (!(me->buttons() & Qt::LeftButton)) {
|
|
stopWindowDrag();
|
|
} else {
|
|
setPos(
|
|
calcDraggedWindowPos(me->screenPos(), me->scenePos(), me->buttonDownScenePos(Qt::LeftButton)));
|
|
}
|
|
me->accept();
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case QEvent::GraphicsSceneMouseRelease:
|
|
if (draggingWindow && me->button() == Qt::LeftButton) {
|
|
stopWindowDrag();
|
|
me->accept();
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return QGraphicsWidget::windowFrameEvent(event);
|
|
}
|
|
|
|
void ZoneViewWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
// move if the scene routes moves while dragging
|
|
if (draggingWindow && (event->buttons() & Qt::LeftButton)) {
|
|
setPos(calcDraggedWindowPos(event->screenPos(), event->scenePos(), event->buttonDownScenePos(Qt::LeftButton)));
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
QGraphicsWidget::mouseMoveEvent(event);
|
|
}
|
|
|
|
void ZoneViewWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if (draggingWindow && event->button() == Qt::LeftButton) {
|
|
stopWindowDrag();
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
QGraphicsWidget::mouseReleaseEvent(event);
|
|
}
|
|
|
|
QVariant ZoneViewWidget::itemChange(GraphicsItemChange change, const QVariant &value)
|
|
{
|
|
if (change == QGraphicsItem::ItemPositionChange && scene()) {
|
|
// Keep grab area in main view
|
|
const QRectF sceneRect = scene()->sceneRect();
|
|
const QPointF requestedPos = value.toPointF();
|
|
QPointF desiredPos = requestedPos;
|
|
|
|
const qreal minX = sceneRect.left();
|
|
const qreal maxX = qMax(minX, sceneRect.right() - kMinVisibleWidth);
|
|
const qreal minY = sceneRect.top() + kTitleBarHeight;
|
|
const qreal maxY = qMax(minY, sceneRect.bottom() - kTitleBarHeight);
|
|
|
|
desiredPos.setX(qBound(minX, desiredPos.x(), maxX));
|
|
desiredPos.setY(qBound(minY, desiredPos.y(), maxY));
|
|
return desiredPos;
|
|
}
|
|
|
|
return QGraphicsWidget::itemChange(change, value);
|
|
}
|
|
|
|
void ZoneViewWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
|
|
{
|
|
// We need to manually resize the scroll bar whenever the window gets resized
|
|
resizeScrollbar(event->newSize().height() - extraHeight - 10);
|
|
}
|
|
|
|
void ZoneViewWidget::resizeScrollbar(const qreal newZoneHeight)
|
|
{
|
|
qreal totalZoneHeight = zone->getOptimumRect().height();
|
|
qreal newMax = qMax(totalZoneHeight - newZoneHeight, 0.0);
|
|
scrollBar->setMaximum(newMax);
|
|
}
|
|
|
|
/**
|
|
* Maps a height that is given as number of rows of cards to the actual height, given in pixels.
|
|
*
|
|
* @param rows Rows of cards
|
|
* @return The height in pixels
|
|
*/
|
|
static qreal rowsToHeight(int rows)
|
|
{
|
|
const qreal cardsHeight = (rows + 1) * (CardDimensions::HEIGHT_F / 3);
|
|
return cardsHeight + 5; // +5 padding to make the cutoff look nicer
|
|
}
|
|
|
|
/**
|
|
* Calculates the max initial height from the settings.
|
|
* The max initial height setting is given as number of rows, so we need to map it to a height.
|
|
**/
|
|
static qreal calcMaxInitialHeight()
|
|
{
|
|
return rowsToHeight(SettingsCache::instance().userInterface().getCardViewInitialRowsMax());
|
|
}
|
|
|
|
/**
|
|
* @brief Handles edge cases in determining the next default zone height. We want the height to snap when the number of
|
|
* rows changes, but not if the player has already expanded the window.
|
|
*/
|
|
static qreal determineNewZoneHeight(qreal oldZoneHeight)
|
|
{
|
|
// don't snap if window is taller than max initial height
|
|
if (oldZoneHeight > calcMaxInitialHeight()) {
|
|
return oldZoneHeight;
|
|
}
|
|
|
|
return calcMaxInitialHeight();
|
|
}
|
|
|
|
void ZoneViewWidget::resizeToZoneContents(bool forceInitialHeight)
|
|
{
|
|
QRectF zoneRect = zone->getOptimumRect();
|
|
qreal totalZoneHeight = zoneRect.height();
|
|
|
|
qreal width = qMax(QGraphicsWidget::layout()->effectiveSizeHint(Qt::MinimumSize, QSizeF()).width(),
|
|
zoneRect.width() + scrollBar->width() + 10);
|
|
|
|
QSizeF maxSize(width, zoneRect.height() + extraHeight + 10);
|
|
|
|
qreal currentZoneHeight = rect().height() - extraHeight - 10;
|
|
qreal newZoneHeight = forceInitialHeight ? calcMaxInitialHeight() : determineNewZoneHeight(currentZoneHeight);
|
|
|
|
QSizeF initialSize(width, newZoneHeight + extraHeight + 10);
|
|
|
|
setMaximumSize(maxSize);
|
|
resize(initialSize);
|
|
resizeScrollbar(newZoneHeight);
|
|
|
|
zone->setGeometry(QRectF(0, -scrollBar->value(), zoneContainer->size().width(), totalZoneHeight));
|
|
|
|
if (layout()) {
|
|
layout()->invalidate();
|
|
}
|
|
}
|
|
|
|
void ZoneViewWidget::handleScrollBarChange(int value)
|
|
{
|
|
zone->setY(-value);
|
|
}
|
|
|
|
void ZoneViewWidget::closeEvent(QCloseEvent *event)
|
|
{
|
|
stopWindowDrag();
|
|
disconnect(zone, &ZoneViewZone::closed, this, 0);
|
|
// manually call zone->close in order to remove it from the origZones views
|
|
zone->close();
|
|
if (shuffleCheckBox.isChecked()) {
|
|
player->getPlayerActions()->sendGameCommand(Command_Shuffle());
|
|
}
|
|
zoneDeleted();
|
|
event->accept();
|
|
}
|
|
|
|
void ZoneViewWidget::zoneDeleted()
|
|
{
|
|
emit closePressed(this);
|
|
deleteLater();
|
|
}
|
|
|
|
void ZoneViewWidget::initStyleOption(QStyleOption *option) const
|
|
{
|
|
QStyleOptionTitleBar *titleBar = qstyleoption_cast<QStyleOptionTitleBar *>(option);
|
|
if (titleBar) {
|
|
titleBar->icon = themePixmap(QStringLiteral("cockatrice"));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Expands/shrinks the window, depending on the current height as well as the configured initial and expanded max
|
|
* heights.
|
|
*/
|
|
void ZoneViewWidget::expandWindow()
|
|
{
|
|
qreal maxInitialHeight = calcMaxInitialHeight();
|
|
qreal maxExpandedHeight = rowsToHeight(SettingsCache::instance().userInterface().getCardViewExpandedRowsMax());
|
|
qreal height = rect().height() - extraHeight - 10;
|
|
qreal maxHeight = maximumHeight() - extraHeight - 10;
|
|
|
|
// reset window to initial max height if...
|
|
bool doResetSize =
|
|
// current height is less than that
|
|
(height < maxInitialHeight) ||
|
|
// current height is at expanded max height
|
|
(height == maxExpandedHeight) ||
|
|
// current height is at actual max height, and actual max height is less than expanded max height
|
|
(height == maxHeight && height > maxInitialHeight && height < maxExpandedHeight);
|
|
|
|
if (doResetSize) {
|
|
resizeToZoneContents(true);
|
|
} else {
|
|
// expand/shrink window to expanded max height if current height is anywhere at or between initial max height
|
|
// and actual max height
|
|
resize(maximumSize().boundedTo({maximumWidth(), maxExpandedHeight + extraHeight + 10}));
|
|
}
|
|
}
|
|
|
|
void ZoneViewWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if (event->pos().y() <= 0) {
|
|
expandWindow();
|
|
}
|
|
}
|