File indexing completed on 2024-05-05 12:27:34
0001 // Copyright (c) 2002-2004 Rob Kaper <cap@capsi.com> 0002 // 0003 // This library is free software; you can redistribute it and/or 0004 // modify it under the terms of the GNU Lesser General Public 0005 // License version 2.1 as published by the Free Software Foundation. 0006 // 0007 // This library is distributed in the hope that it will be useful, 0008 // but WITHOUT ANY WARRANTY; without even the implied warranty of 0009 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0010 // Lesser General Public License for more details. 0011 // 0012 // You should have received a copy of the GNU Lesser General Public License 0013 // along with this library; see the file COPYING.LIB. If not, write to 0014 // the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0015 // Boston, MA 02110-1301, USA. 0016 0017 #include <iostream> 0018 0019 #include <QGridLayout> 0020 #include <QResizeEvent> 0021 0022 #include <klocalizedstring.h> 0023 0024 #include <atlantic_core.h> 0025 #include <player.h> 0026 #include <estate.h> 0027 #include <auction.h> 0028 #include <game.h> 0029 0030 #include "auction_widget.h" 0031 #include "configoption.h" 0032 #include "estatedetails.h" 0033 #include "estateview.h" 0034 #include "token.h" 0035 0036 #include "board.h" 0037 0038 #include <libatlantikui_debug.h> 0039 0040 AtlantikBoard::AtlantikBoard(AtlanticCore *atlanticCore, int maxEstates, DisplayMode mode, QWidget *parent) 0041 : QWidget(parent) 0042 , m_atlanticCore(atlanticCore) 0043 , m_mode(mode) 0044 , m_lastServerDisplay(nullptr) 0045 , m_lastServerDisplayBeforeAuction(nullptr) 0046 , m_movingToken(nullptr) 0047 , m_resumeTimer(false) 0048 , m_animateTokens(false) 0049 , m_maxEstates(maxEstates) 0050 { 0051 setMinimumSize(QSize(500, 500)); 0052 0053 int sideLen = maxEstates/4; 0054 0055 // Animated token movement 0056 m_timer = new QTimer(this); 0057 connect(m_timer, SIGNAL(timeout()), this, SLOT(slotMoveToken())); 0058 0059 m_gridLayout = new QGridLayout( this ); 0060 m_gridLayout->setSpacing(0); 0061 m_gridLayout->setContentsMargins(0, 0, 0, 0); 0062 for(int i=0;i<=sideLen;i++) 0063 { 0064 if (i==0 || i==sideLen) 0065 { 0066 m_gridLayout->setRowStretch(i, 3); 0067 m_gridLayout->setColumnStretch(i, 3); 0068 } 0069 else 0070 { 0071 m_gridLayout->setRowStretch(i, 2); 0072 m_gridLayout->setColumnStretch(i, 2); 0073 } 0074 } 0075 0076 // spacer = new QWidget(this); 0077 // m_gridLayout->addWidget(spacer, sideLen, sideLen); // SE 0078 0079 displayDefault(); 0080 } 0081 0082 AtlantikBoard::~AtlantikBoard() 0083 { 0084 reset(); 0085 } 0086 0087 void AtlantikBoard::reset() 0088 { 0089 qCDebug(LIBATLANTIKUI_LOG); 0090 0091 qDeleteAll(m_tokens); 0092 m_tokens.clear(); 0093 qDeleteAll(m_estateViews); 0094 m_estateViews.clear(); 0095 qDeleteAll(m_displayQueue); 0096 m_displayQueue.clear(); 0097 m_lastServerDisplay = nullptr; 0098 m_movingToken = nullptr; 0099 } 0100 0101 void AtlantikBoard::setViewProperties(bool indicateUnowned, bool highlightUnowned, bool darkenMortgaged, bool quartzEffects, bool animateTokens) 0102 { 0103 if (m_animateTokens != animateTokens) 0104 m_animateTokens = animateTokens; 0105 0106 // Update EstateViews 0107 foreach (EstateView *estateView, m_estateViews) 0108 estateView->setViewProperties(indicateUnowned, highlightUnowned, darkenMortgaged, quartzEffects); 0109 } 0110 0111 int AtlantikBoard::heightForWidth(int width) const 0112 { 0113 return width; 0114 } 0115 0116 EstateView *AtlantikBoard::findEstateView(Estate *estate) const 0117 { 0118 return m_estateViews.value(estate, nullptr); 0119 } 0120 0121 void AtlantikBoard::addEstateView(Estate *estate, bool indicateUnowned, bool highlightUnowned, bool darkenMortgaged, bool quartzEffects) 0122 { 0123 QString icon = estate->icon(); 0124 int estateId = estate->id(); 0125 EstateOrientation orientation = North; 0126 int sideLen = m_gridLayout->rowCount() - 1; 0127 0128 if (estateId < sideLen) 0129 orientation = North; 0130 else if (estateId < 2*sideLen) 0131 orientation = East; 0132 else if (estateId < 3*sideLen) 0133 orientation = South; 0134 else //if (estateId < 4*sideLen) 0135 orientation = West; 0136 0137 EstateView *estateView = new EstateView(estate, orientation, icon, indicateUnowned, highlightUnowned, darkenMortgaged, quartzEffects, this); 0138 estateView->setObjectName(QStringLiteral("estateview")); 0139 estateView->setAllowEstateSales(true); // XXX should use the allowestatesales config option 0140 m_estateViews.insert(estate, estateView); 0141 0142 connect(estate, SIGNAL(changed()), estateView, SLOT(estateChanged())); 0143 connect(estateView, SIGNAL(estateToggleMortgage(Estate *)), estate, SIGNAL(estateToggleMortgage(Estate *))); 0144 connect(estateView, SIGNAL(LMBClicked(Estate *)), estate, SIGNAL(LMBClicked(Estate *))); 0145 connect(estateView, SIGNAL(estateHouseBuy(Estate *)), estate, SIGNAL(estateHouseBuy(Estate *))); 0146 connect(estateView, SIGNAL(estateHouseSell(Estate *)), estate, SIGNAL(estateHouseSell(Estate *))); 0147 connect(estateView, SIGNAL(estateSell(Estate *)), estate, SIGNAL(estateSell(Estate *))); 0148 connect(estateView, SIGNAL(newTrade(Player *)), estate, SIGNAL(newTrade(Player *))); 0149 0150 // Designer has its own LMBClicked slot 0151 if (m_mode == Play) 0152 connect(estateView, SIGNAL(LMBClicked(Estate *)), this, SLOT(prependEstateDetails(Estate *))); 0153 0154 if (estateId<sideLen) 0155 m_gridLayout->addWidget(estateView, sideLen, sideLen-estateId); 0156 else if (estateId<2*sideLen) 0157 m_gridLayout->addWidget(estateView, 2*sideLen-estateId, 0); 0158 else if (estateId<3*sideLen) 0159 m_gridLayout->addWidget(estateView, 0, estateId-2*sideLen); 0160 else 0161 m_gridLayout->addWidget(estateView, estateId-3*sideLen, sideLen); 0162 0163 estateView->show(); 0164 0165 if (m_atlanticCore) 0166 { 0167 foreach (Player *player, m_atlanticCore->players()) 0168 if (player->location() == estate) 0169 addToken(player); 0170 } 0171 } 0172 0173 void AtlantikBoard::addAuctionWidget(Auction *auction) 0174 { 0175 AuctionWidget *auctionW = new AuctionWidget(m_atlanticCore, auction, this); 0176 m_lastServerDisplayBeforeAuction = m_lastServerDisplay; 0177 m_lastServerDisplay = auctionW; 0178 m_displayQueue.prepend(auctionW); 0179 updateCenter(); 0180 0181 connect(auction, SIGNAL(completed()), this, SLOT(displayDefault())); 0182 } 0183 0184 Token *AtlantikBoard::findToken(Player *player) const 0185 { 0186 return m_tokens.value(player, nullptr); 0187 } 0188 0189 void AtlantikBoard::addToken(Player *player) 0190 { 0191 if (!player->location()) 0192 { 0193 qCDebug(LIBATLANTIKUI_LOG) << "addToken ignored - estateView null"; 0194 return; 0195 } 0196 0197 if (player->isSpectator()) 0198 { 0199 qCDebug(LIBATLANTIKUI_LOG) << "addToken ignored - is a spectator"; 0200 return; 0201 } 0202 0203 Player *playerSelf = nullptr; 0204 if (m_atlanticCore) 0205 playerSelf = m_atlanticCore->playerSelf(); 0206 0207 if (playerSelf && playerSelf->game() != player->game() ) 0208 { 0209 qCDebug(LIBATLANTIKUI_LOG) << "addToken ignored - not in same game as playerSelf"; 0210 return; 0211 } 0212 0213 qCDebug(LIBATLANTIKUI_LOG) << "addToken"; 0214 0215 Token *token = new Token(player, this); 0216 token->setObjectName(QStringLiteral("token")); 0217 token->setTokenTheme(m_tokenTheme); 0218 m_tokens.insert(player, token); 0219 connect(player, SIGNAL(changed(Player *)), token, SLOT(playerChanged())); 0220 0221 jumpToken(token); 0222 0223 // Timer to reinit the gameboard _after_ event loop 0224 QTimer::singleShot(100, this, SLOT(slotResizeAftermath())); 0225 } 0226 0227 void AtlantikBoard::playerChanged(Player *player) 0228 { 0229 qCDebug(LIBATLANTIKUI_LOG) << "playerLoc" << (player->location() ? player->location()->name() : QStringLiteral("none")); 0230 0231 Player *playerSelf = nullptr; 0232 if (m_atlanticCore) 0233 playerSelf = m_atlanticCore->playerSelf(); 0234 0235 // Update token 0236 Token *token = findToken(player); 0237 if (token) 0238 { 0239 qCDebug(LIBATLANTIKUI_LOG) << "tokenLoc" << (token->location() ? token->location()->name() : QStringLiteral("none")); 0240 if (player->isBankrupt() || player->isSpectator() || (playerSelf && playerSelf->game() != player->game()) ) 0241 token->hide(); 0242 if (!player->isSpectator()) 0243 token->show(); 0244 if (player->hasTurn()) 0245 token->raise(); 0246 0247 bool jump = false, move = false; 0248 0249 if (token->inJail() != player->inJail()) 0250 { 0251 token->setInJail(player->inJail()); 0252 0253 // If any status such as jail is ever allowed to 0254 // change in the future, during movement, this needs 0255 // to be addressed in moveToken and subsequent steps. 0256 if (token != m_movingToken) 0257 jump = true; 0258 } 0259 0260 if (token->location() != player->location()) 0261 { 0262 token->setLocation(player->location()); 0263 jump = true; 0264 } 0265 0266 if (player->destination() && token->destination() != player->destination()) 0267 { 0268 if (m_animateTokens) 0269 { 0270 token->setDestination(player->destination()); 0271 move = true; 0272 } 0273 else 0274 { 0275 token->setLocation(player->destination()); 0276 jump = true; 0277 } 0278 } 0279 0280 if (move) 0281 moveToken(token); 0282 else if (jump) 0283 jumpToken(token); 0284 } 0285 else 0286 addToken(player); 0287 } 0288 0289 void AtlantikBoard::removeToken(Player *player) 0290 { 0291 Token *token = m_tokens.take(player); 0292 if (!token) 0293 return; 0294 0295 if (token == m_movingToken) 0296 { 0297 m_timer->stop(); 0298 m_movingToken = nullptr; 0299 } 0300 0301 delete token; 0302 } 0303 0304 void AtlantikBoard::jumpToken(Token *token) 0305 { 0306 if (!token || !token->location()) 0307 return; 0308 0309 qCDebug(LIBATLANTIKUI_LOG) << "to" << token->location()->name(); 0310 0311 QPoint tGeom = calculateTokenDestination(token); 0312 token->move(tGeom); 0313 0314 Player *player = token->player(); 0315 if (player) 0316 { 0317 player->setLocation(token->location()); 0318 player->setDestination(nullptr); 0319 0320 if (token->isHidden() && !player->isBankrupt() && !player->isSpectator()) 0321 token->show(); 0322 } 0323 0324 if (token == m_movingToken) 0325 { 0326 m_timer->stop(); 0327 0328 if (!m_resumeTimer) 0329 m_movingToken = nullptr; 0330 } 0331 0332 Q_EMIT tokenConfirmation(token->location()); 0333 } 0334 0335 void AtlantikBoard::moveToken(Token *token) 0336 { 0337 qCDebug(LIBATLANTIKUI_LOG) << "to" << token->destination()->name(); 0338 0339 m_movingToken = token; 0340 0341 // Start timer 0342 m_timer->start(15); 0343 } 0344 0345 QPoint AtlantikBoard::calculateTokenDestination(Token *token, Estate *eDest) 0346 { 0347 if (!eDest) 0348 eDest = token->location(); 0349 0350 EstateView *evDest = findEstateView(eDest); 0351 if (!evDest) 0352 return QPoint(0, 0); 0353 0354 int x = 0, y = 0; 0355 const QRect evDestGeom = evDest->geometry(); 0356 if (token->player()->inJail()) 0357 { 0358 x = evDestGeom.right() - token->width() - 2; 0359 y = evDestGeom.top(); 0360 } 0361 else 0362 { 0363 const QPoint c = evDestGeom.center(); 0364 x = c.x() - (token->width()/2); 0365 y = c.y() - (token->height()/2); 0366 0367 /* 0368 // Re-center because of EstateView headers 0369 switch(evDest->orientation()) 0370 { 0371 case North: 0372 y += evDest->height()/8; break; 0373 case East: 0374 x -= evDest->width()/8; break; 0375 case South: 0376 y -= evDest->height()/8; break; 0377 case West: 0378 x += evDest->width()/8; break; 0379 } 0380 */ 0381 } 0382 return QPoint(x, y); 0383 } 0384 0385 void AtlantikBoard::slotMoveToken() 0386 { 0387 // Requires a core with estates to operate on 0388 if (!m_atlanticCore) 0389 { 0390 qCDebug(LIBATLANTIKUI_LOG) << "ignored - no atlanticCore"; 0391 return; 0392 } 0393 0394 // Do we actually have a token to move? 0395 if (!m_movingToken) 0396 { 0397 m_timer->stop(); 0398 return; 0399 } 0400 0401 // Where are we? 0402 QPoint tokenPos = m_movingToken->pos(); 0403 int xCurrent = tokenPos.x(); 0404 int yCurrent = tokenPos.y(); 0405 0406 // Where do we want to go today? 0407 Estate *eDest = m_atlanticCore->estateAfter(m_movingToken->location()); 0408 QPoint tGeom = calculateTokenDestination(m_movingToken, eDest); 0409 0410 int xDest = tGeom.x(); 0411 int yDest = tGeom.y(); 0412 0413 if (xDest - xCurrent > 1) 0414 xDest = xCurrent + 2; 0415 else if (xCurrent - xDest > 1) 0416 xDest = xCurrent - 2; 0417 else 0418 xDest = xCurrent; 0419 0420 if (yDest - yCurrent > 1) 0421 yDest = yCurrent + 2; 0422 else if (yCurrent - yDest > 1) 0423 yDest = yCurrent - 2; 0424 else 0425 yDest = yCurrent; 0426 0427 // qCDebug(LIBATLANTIKUI_LOG) << "TOKEN: at " << xCurrent << "," << yCurrent << " and going to " << xDest << "," << yDest; 0428 0429 if (xCurrent != xDest || yCurrent != yDest) 0430 { 0431 tokenPos.setX(xDest); 0432 tokenPos.setY(yDest); 0433 m_movingToken->move(tokenPos); 0434 return; 0435 } 0436 0437 // We have arrived at our destination! 0438 m_movingToken->setLocation(eDest); 0439 m_movingToken->player()->setLocation(eDest); 0440 Q_EMIT tokenConfirmation(eDest); 0441 0442 // We have arrived at our _final_ destination! 0443 if (eDest == m_movingToken->destination()) 0444 { 0445 m_movingToken->setDestination(nullptr); 0446 m_movingToken->player()->setDestination(nullptr); 0447 0448 m_timer->stop(); 0449 m_movingToken = nullptr; 0450 } 0451 } 0452 0453 void AtlantikBoard::resizeEvent(QResizeEvent *) 0454 { 0455 // Stop moving tokens, slotResizeAftermath will re-enable this 0456 if (m_timer!=nullptr && m_timer->isActive()) 0457 { 0458 m_timer->stop(); 0459 m_resumeTimer=true; 0460 } 0461 0462 /* 0463 // Adjust spacer to make sure board stays a square 0464 int q = e->size().width() - e->size().height(); 0465 if (q > 0) 0466 { 0467 QSize s(q, 0); 0468 spacer->setFixedSize(s); 0469 } 0470 else 0471 { 0472 QSize s(0, -q); 0473 spacer->setFixedSize(s); 0474 } 0475 */ 0476 // Timer to reinit the gameboard _after_ resizeEvent 0477 QTimer::singleShot(0, this, SLOT(slotResizeAftermath())); 0478 } 0479 0480 void AtlantikBoard::slotResizeAftermath() 0481 { 0482 qCDebug(LIBATLANTIKUI_LOG); 0483 // Move tokens back to their last known location (this has to be done 0484 // _after_ resizeEvent has returned to make sure we have the correct 0485 // adjusted estate geometries. 0486 0487 foreach (Token *token, m_tokens) 0488 jumpToken(token); 0489 0490 // Restart the timer that was stopped in resizeEvent 0491 if (m_resumeTimer && m_timer!=nullptr && !m_timer->isActive()) 0492 { 0493 m_timer->start(15); 0494 m_resumeTimer=false; 0495 } 0496 } 0497 0498 void AtlantikBoard::displayDefault() 0499 { 0500 switch(m_displayQueue.count()) 0501 { 0502 case 0: 0503 m_displayQueue.prepend(new QWidget(this)); 0504 break; 0505 case 1: 0506 if (EstateDetails *display = dynamic_cast<EstateDetails*>(m_lastServerDisplay)) 0507 display->setEstate(nullptr); 0508 break; 0509 default: 0510 if (m_displayQueue.first() == m_lastServerDisplay) 0511 m_lastServerDisplay = nullptr; 0512 delete m_displayQueue.takeFirst(); 0513 if (m_lastServerDisplayBeforeAuction) 0514 m_lastServerDisplay = m_lastServerDisplayBeforeAuction; 0515 break; 0516 } 0517 updateCenter(); 0518 } 0519 0520 void AtlantikBoard::displayButton(const QString &command, const QString &caption, bool enabled) 0521 { 0522 if (EstateDetails *display = dynamic_cast<EstateDetails*>(m_lastServerDisplay)) 0523 display->addButton(command, caption, enabled); 0524 } 0525 0526 void AtlantikBoard::addCloseButton() 0527 { 0528 EstateDetails *eDetails = nullptr; 0529 if ((eDetails = dynamic_cast<EstateDetails*>(m_lastServerDisplay)) && eDetails != m_displayQueue.last()) 0530 eDetails->addCloseButton(); 0531 } 0532 0533 void AtlantikBoard::insertDetails(const QString &text, bool clearText, bool clearButtons, Estate *estate) 0534 { 0535 EstateDetails *eDetails = nullptr; 0536 0537 if ((eDetails = dynamic_cast<EstateDetails*>(m_lastServerDisplay))) 0538 { 0539 if (clearText) 0540 eDetails->setText(text); 0541 else 0542 eDetails->appendText(text); 0543 0544 if (clearButtons) 0545 eDetails->clearButtons(); 0546 0547 eDetails->setEstate(estate); 0548 return; 0549 } 0550 0551 if (!m_displayQueue.isEmpty() && m_displayQueue.first() != m_lastServerDisplay) 0552 delete m_displayQueue.takeFirst(); 0553 0554 eDetails = new EstateDetails(estate, text, this); 0555 m_lastServerDisplay = eDetails; 0556 connect(eDetails, SIGNAL(buttonCommand(QString)), this, SIGNAL(buttonCommand(QString))); 0557 connect(eDetails, SIGNAL(buttonClose()), this, SLOT(displayDefault())); 0558 0559 m_displayQueue.insert(0, eDetails); 0560 updateCenter(); 0561 } 0562 0563 void AtlantikBoard::insertText(const QString &text, bool clearText, bool clearButtons) 0564 { 0565 EstateDetails *eDetails = dynamic_cast<EstateDetails*>(m_lastServerDisplay); 0566 if (!eDetails) 0567 return; 0568 0569 if (clearText) 0570 eDetails->setText(text); 0571 else 0572 eDetails->appendText(text); 0573 0574 if (clearButtons) 0575 eDetails->clearButtons(); 0576 } 0577 0578 void AtlantikBoard::prependEstateDetails(Estate *estate) 0579 { 0580 if (!estate) 0581 return; 0582 0583 EstateDetails *eDetails = nullptr; 0584 0585 if (m_displayQueue.first() == m_lastServerDisplay) 0586 { 0587 eDetails = new EstateDetails(estate, QString(), this); 0588 m_displayQueue.prepend(eDetails); 0589 0590 connect(eDetails, SIGNAL(buttonCommand(QString)), this, SIGNAL(buttonCommand(QString))); 0591 connect(eDetails, SIGNAL(buttonClose()), this, SLOT(displayDefault())); 0592 } 0593 else 0594 { 0595 eDetails = dynamic_cast<EstateDetails*>(m_displayQueue.first()); 0596 if (eDetails) 0597 { 0598 eDetails->setEstate(estate); 0599 eDetails->setText( QString() ); 0600 // eDetails->clearButtons(); 0601 } 0602 else 0603 { 0604 qCDebug(LIBATLANTIKUI_LOG) << "manual estatedetails with first in queue neither server nor details"; 0605 return; 0606 } 0607 } 0608 0609 eDetails->addDetails(); 0610 eDetails->addCloseButton(); 0611 0612 updateCenter(); 0613 } 0614 0615 void AtlantikBoard::updateCenter() 0616 { 0617 QWidget *center = m_displayQueue.first(); 0618 m_gridLayout->addWidget(center, 1, 1, m_gridLayout->rowCount()-2, m_gridLayout->columnCount()-2); 0619 center->show(); 0620 } 0621 0622 QWidget *AtlantikBoard::centerWidget() 0623 { 0624 return m_displayQueue.first(); 0625 } 0626 0627 void AtlantikBoard::setTokenTheme(const TokenTheme &theme) 0628 { 0629 m_tokenTheme = theme; 0630 foreach (Token *token, m_tokens) 0631 token->setTokenTheme(m_tokenTheme); 0632 } 0633 0634 #include "moc_board.cpp"