File indexing completed on 2024-05-05 05:40:29

0001 /***************************************************************************
0002  *  Copyright (C) 2021 by Renaud Guezennec                               *
0003  *   http://www.rolisteam.org/contact                                      *
0004  *                                                                         *
0005  *   This software is free software; you can redistribute it and/or modify *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; if not, write to the                         *
0017  *   Free Software Foundation, Inc.,                                       *
0018  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
0019  ***************************************************************************/
0020 #include "model/nonplayablecharactermodel.h"
0021 #include "worker/iohelper.h"
0022 namespace campaign
0023 {
0024 NonPlayableCharacter::NonPlayableCharacter(QObject* parent) : Character(parent)
0025 {
0026     setNpc(true);
0027 }
0028 
0029 QStringList NonPlayableCharacter::tags() const
0030 {
0031     return m_tags;
0032 }
0033 
0034 QString NonPlayableCharacter::avatarPath() const
0035 {
0036     return m_avatarPath;
0037 }
0038 
0039 int NonPlayableCharacter::size() const
0040 {
0041     return m_size;
0042 }
0043 
0044 QString NonPlayableCharacter::description() const
0045 {
0046     return m_description;
0047 }
0048 
0049 void NonPlayableCharacter::setTags(const QStringList& list)
0050 {
0051     if(list == m_tags)
0052         return;
0053     m_tags= list;
0054     emit tagsChanged();
0055 }
0056 
0057 void NonPlayableCharacter::setAvatarPath(const QString& path)
0058 {
0059     if(path == m_avatarPath)
0060         return;
0061     m_avatarPath= path;
0062     emit avatarPathChanged();
0063 }
0064 
0065 void NonPlayableCharacter::setSize(int size)
0066 {
0067     if(size == m_size)
0068         return;
0069     m_size= size;
0070     emit sizeChanged();
0071 }
0072 
0073 void NonPlayableCharacter::setDescription(const QString& desc)
0074 {
0075     if(desc == m_description)
0076         return;
0077     m_description= desc;
0078     emit descriptionChanged();
0079 }
0080 
0081 NonPlayableCharacterModel::NonPlayableCharacterModel(CharacterStateModel* states, QObject* parent)
0082     : QAbstractListModel(parent), m_states(states)
0083 {
0084     m_header << tr("Avatar") << tr("Name") << tr("Description") << tr("GM Details") << tr("Tags") << tr("Color")
0085              << tr("Min HP") << tr("Current HP") << tr("Max HP") << tr("Initiative") << tr("Distance Per turn")
0086              << tr("State") << tr("Life Color") << tr("Init Command");
0087 }
0088 
0089 QVariant NonPlayableCharacterModel::headerData(int section, Qt::Orientation orientation, int role) const
0090 {
0091     if(orientation == Qt::Vertical)
0092         return {};
0093 
0094     if(Qt::DisplayRole == role)
0095     {
0096         return m_header[section];
0097     }
0098 
0099     return {};
0100 }
0101 
0102 int NonPlayableCharacterModel::rowCount(const QModelIndex& parent) const
0103 {
0104     if(parent.isValid())
0105         return 0;
0106 
0107     return m_data.size();
0108 }
0109 
0110 int NonPlayableCharacterModel::columnCount(const QModelIndex&) const
0111 {
0112     return m_header.size();
0113 }
0114 
0115 QVariant NonPlayableCharacterModel::data(const QModelIndex& index, int role) const
0116 {
0117     static QSet<int> acceptedRole({Qt::DecorationRole,
0118                                    Qt::DisplayRole,
0119                                    Qt::TextAlignmentRole,
0120                                    Qt::BackgroundRole,
0121                                    Qt::EditRole,
0122                                    RoleUuid,
0123                                    RoleAvatar,
0124                                    RoleName,
0125                                    RoleDescription,
0126                                    RoleGmDetails,
0127                                    RoleTags,
0128                                    RoleColor,
0129                                    RoleMinHp,
0130                                    RoleCurrentHp,
0131                                    RoleMaxHp,
0132                                    RoleInitiative,
0133                                    RoleDistancePerTurn,
0134                                    RoleState,
0135                                    RoleLifeColor,
0136                                    RoleInitCommand,
0137                                    RoleUuid,
0138                                    RoleAvatarPath,
0139                                    RoleHasInitiative,
0140                                    RoleActionCount,
0141                                    RoleShapeCount,
0142                                    RolePropertiesCount});
0143     if(!index.isValid() || !acceptedRole.contains(role))
0144         return QVariant();
0145 
0146     if(role == Qt::TextAlignmentRole)
0147         return Qt::AlignCenter;
0148 
0149     auto customRole= role > Qt::UserRole ? role : Qt::UserRole + 1 + index.column();
0150 
0151     if(role == Qt::BackgroundRole && (customRole != RoleColor && customRole != RoleLifeColor))
0152         return {};
0153 
0154     if(role == Qt::DecorationRole && (customRole != RoleAvatar))
0155         return {};
0156 
0157     auto const& character= m_data[index.row()];
0158     QVariant res;
0159     switch(customRole)
0160     {
0161     case RoleAvatar:
0162         if(role == Qt::EditRole)
0163             res= QString{};
0164         else
0165             res= QVariant::fromValue(IOHelper::dataToPixmap(character->avatar()));
0166         break;
0167     case RoleUuid:
0168         res= character->uuid();
0169         break;
0170     case RoleName:
0171         res= character->name();
0172         break;
0173     case RoleDescription:
0174         res= character->description();
0175         break;
0176     case RoleColor:
0177         res= character->getColor();
0178         break;
0179     case RoleMinHp:
0180         res= character->getHealthPointsMin();
0181         break;
0182     case RoleCurrentHp:
0183         res= character->getHealthPointsCurrent();
0184         break;
0185     case RoleMaxHp:
0186         res= character->getHealthPointsMax();
0187         break;
0188     case RoleInitiative:
0189         res= character->getInitiativeScore();
0190         break;
0191     case RoleDistancePerTurn:
0192         res= character->getDistancePerTurn();
0193         break;
0194     case RoleState:
0195         if(role == Qt::DisplayRole)
0196             res= stateName(character->stateId());
0197         else if(role == Qt::EditRole || role == RoleState)
0198             res= character->stateId();
0199         break;
0200     case RoleLifeColor:
0201         res= character->getLifeColor();
0202         break;
0203     case RoleInitCommand:
0204         res= character->initCommand();
0205         break;
0206     case RoleTags:
0207         if(role == Qt::DisplayRole || role == RoleTags)
0208             res= character->tags();
0209         else if(role == Qt::EditRole)
0210             res= character->tags().join(";");
0211         break;
0212     case RoleAvatarPath:
0213         res= character->avatarPath();
0214         break;
0215     case RoleHasInitiative:
0216         res= character->hasInitScore();
0217         break;
0218     case RoleActionCount:
0219         res= character->actionList().size();
0220         break;
0221     case RoleShapeCount:
0222         res= character->shapeList().size();
0223         break;
0224     case RolePropertiesCount:
0225         res= character->propertiesList().size();
0226         break;
0227     case RoleGmDetails:
0228         res= character->gameMasterDesc();
0229         break;
0230     }
0231 
0232     return res;
0233 }
0234 
0235 bool NonPlayableCharacterModel::setData(const QModelIndex& index, const QVariant& value, int role)
0236 {
0237     if(!index.isValid())
0238         return false;
0239 
0240     auto customRole= Qt::UserRole + 1 + index.column();
0241     if(role > Qt::UserRole)
0242         customRole= role;
0243 
0244     if(data(index, role) != value)
0245     {
0246 
0247         bool res= true;
0248         auto const& character= m_data[index.row()];
0249         switch(customRole)
0250         {
0251         case RoleName:
0252             character->setName(value.toString());
0253             break;
0254         case RoleDescription:
0255             character->setDescription(value.toString());
0256             break;
0257         case RoleColor:
0258             character->setColor(value.value<QColor>());
0259             break;
0260         case RoleMinHp:
0261             character->setHealthPointsMin(value.toInt());
0262             break;
0263         case RoleCurrentHp:
0264             character->setHealthPointsCurrent(value.toInt());
0265             break;
0266         case RoleMaxHp:
0267             character->setHealthPointsMax(value.toInt());
0268             break;
0269         case RoleInitiative:
0270             character->setInitiativeScore(value.toInt());
0271             break;
0272         case RoleDistancePerTurn:
0273             character->setDistancePerTurn(value.toInt());
0274             break;
0275         case RoleState:
0276             character->setStateId(value.toString());
0277             break;
0278         case RoleLifeColor:
0279             character->setLifeColor(value.value<QColor>());
0280             break;
0281         case RoleInitCommand:
0282             character->setInitCommand(value.toString());
0283             break;
0284         case RoleTags:
0285             character->setTags(value.toString().split(';'));
0286             break;
0287         case RoleAvatar:
0288             if(value.typeId() == qMetaTypeId<QByteArray>())
0289             {
0290                 character->setAvatar(value.toByteArray());
0291             }
0292             else if(value.typeId() == qMetaTypeId<QString>())
0293             {
0294                 auto str= value.toString();
0295                 if(str.isEmpty())
0296                     return false;
0297                 auto path= IOHelper::copyImageFileIntoCampaign(str, m_npcRoot);
0298                 character->setAvatarPath(path);
0299                 character->setAvatar(IOHelper::imageToData(IOHelper::readImageFromURL(QUrl::fromUserInput(path))));
0300             }
0301             else if(value.typeId() == qMetaTypeId<QUrl>())
0302             {
0303                 character->setAvatar(IOHelper::imageToData(IOHelper::readImageFromURL(value.toUrl())));
0304             }
0305             break;
0306         case RoleAvatarPath:
0307             character->setAvatarPath(value.toString());
0308             break;
0309         default:
0310             res= false;
0311             break;
0312         }
0313         if(res)
0314             emit dataChanged(index, index, QVector<int>() << role);
0315         return res;
0316     }
0317     return false;
0318 }
0319 
0320 QString NonPlayableCharacterModel::stateName(const QString& id) const
0321 {
0322     if(!m_states)
0323         return id;
0324 
0325     return m_states->stateLabelFromId(id);
0326 }
0327 
0328 Qt::DropActions NonPlayableCharacterModel::supportedDropActions() const
0329 {
0330     return Qt::MoveAction | Qt::CopyAction | Qt::TargetMoveAction | Qt::ActionMask ;
0331 }
0332 
0333 bool NonPlayableCharacterModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
0334 {
0335     if(column != NonPlayableCharacterModel::ColAvatar || action == Qt::IgnoreAction)
0336         return false;
0337 
0338     bool added= false;
0339 
0340     if(data->hasImage())
0341     {
0342         setData(index(row, column), IOHelper::imageToData(data->imageData().value<QImage>()),NonPlayableCharacterModel::RoleAvatar);
0343     }
0344     else if(data->hasUrls())
0345     {
0346         auto urls = data->urls();
0347         if(urls.size() > 1 || urls.isEmpty())
0348             return false;
0349         setData(index(row, column), urls[0],NonPlayableCharacterModel::RoleAvatar);
0350         setData(index(row, column), urls[0],NonPlayableCharacterModel::RoleAvatarPath);
0351     }
0352     return added;
0353 }
0354 
0355 Qt::ItemFlags NonPlayableCharacterModel::flags(const QModelIndex& index) const
0356 {
0357     if(!index.isValid())
0358         return Qt::NoItemFlags;
0359 
0360     if(index.column() == NonPlayableCharacterModel::ColAvatar)
0361         return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
0362     else
0363         return Qt::ItemIsDragEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
0364 }
0365 
0366 void NonPlayableCharacterModel::append()
0367 {
0368     addCharacter(new NonPlayableCharacter);
0369 }
0370 
0371 void NonPlayableCharacterModel::addCharacter(NonPlayableCharacter* character)
0372 {
0373     auto size= static_cast<int>(m_data.size());
0374     beginInsertRows(QModelIndex(), size, size);
0375     std::unique_ptr<NonPlayableCharacter> uni{character};
0376     m_data.push_back(std::move(uni));
0377     endInsertRows();
0378     emit characterAdded();
0379 }
0380 
0381 void NonPlayableCharacterModel::setModelData(const std::vector<NonPlayableCharacter*>& data)
0382 {
0383     beginResetModel();
0384     m_data.clear();
0385     std::for_each(std::begin(data), std::end(data),
0386                   [this](NonPlayableCharacter* npc)
0387                   {
0388                       std::unique_ptr<NonPlayableCharacter> unique(npc);
0389                       m_data.push_back(std::move(unique));
0390                   });
0391     endResetModel();
0392 }
0393 
0394 void NonPlayableCharacterModel::removeNpc(const QString& uuid)
0395 {
0396     auto index= indexFromUuid(uuid);
0397     if(!index.isValid())
0398         return;
0399 
0400     beginRemoveRows(QModelIndex(), index.row(), index.row());
0401     auto const& it= m_data.begin() + index.row();
0402     m_data.erase(it);
0403     endRemoveRows();
0404 
0405     emit characterRemoved(uuid);
0406 }
0407 
0408 void NonPlayableCharacterModel::refresh(const QString& uuid)
0409 {
0410     auto index= indexFromUuid(uuid);
0411     if(!index.isValid())
0412         return;
0413 
0414     emit dataChanged(index, index);
0415 }
0416 
0417 QModelIndex NonPlayableCharacterModel::indexFromUuid(const QString& id)
0418 {
0419     auto it= std::find_if(std::begin(m_data), std::end(m_data),
0420                           [id](const std::unique_ptr<NonPlayableCharacter>& character)
0421                           { return character->uuid() == id; });
0422 
0423     if(it == std::end(m_data))
0424         return {};
0425     else
0426         return index(std::distance(std::begin(m_data), it), 0);
0427 }
0428 
0429 NonPlayableCharacter* NonPlayableCharacterModel::characterFromUuid(const QString& id)
0430 {
0431     auto it= std::find_if(std::begin(m_data), std::end(m_data),
0432                           [id](const std::unique_ptr<NonPlayableCharacter>& character)
0433                           { return character->uuid() == id; });
0434 
0435     if(it != std::end(m_data))
0436         return it->get();
0437 
0438     return nullptr;
0439 }
0440 
0441 const std::vector<std::unique_ptr<NonPlayableCharacter>>& NonPlayableCharacterModel::npcList() const
0442 {
0443     return m_data;
0444 }
0445 
0446 QStringList NonPlayableCharacterModel::headers()
0447 {
0448     return m_header;
0449 }
0450 
0451 const QString& NonPlayableCharacterModel::npcRoot() const
0452 {
0453     return m_npcRoot;
0454 }
0455 
0456 void NonPlayableCharacterModel::setNpcRoot(const QString& newNpcRoot)
0457 {
0458     if(m_npcRoot == newNpcRoot)
0459         return;
0460     m_npcRoot= newNpcRoot;
0461     emit npcRootChanged();
0462 }
0463 
0464 const QString& NonPlayableCharacter::gameMasterDesc() const
0465 {
0466     return m_gameMasterDesc;
0467 }
0468 
0469 void NonPlayableCharacter::setGameMasterDesc(const QString& newGameMasterDesc)
0470 {
0471     if(m_gameMasterDesc == newGameMasterDesc)
0472         return;
0473     m_gameMasterDesc= newGameMasterDesc;
0474     emit gameMasterDescChanged();
0475 }
0476 
0477 } // namespace campaign