File indexing completed on 2025-01-12 06:47:26
0001 // 0002 // C++ Implementation: clist 0003 // 0004 // Description: 0005 // 0006 /* 0007 Copyright 2007-2011 Tomas Mecir <kmuddy@kmuddy.com> 0008 0009 This program is free software; you can redistribute it and/or 0010 modify it under the terms of the GNU General Public License as 0011 published by the Free Software Foundation; either version 2 of 0012 the License, or (at your option) any later version. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include "clist.h" 0024 0025 #include "clistgroup.h" 0026 #include "clistmanager.h" 0027 0028 #include <KLocalizedString> 0029 0030 #include <QAbstractItemModel> 0031 #include <QFont> 0032 #include <QIcon> 0033 #include <QMimeData> 0034 #include <QXmlStreamReader> 0035 #include <QXmlStreamWriter> 0036 0037 #include <map> 0038 0039 using namespace std; 0040 0041 /** cListModel - the model providing access to a single list. */ 0042 class cListModel : public QAbstractItemModel { 0043 0044 friend class cList; 0045 0046 cListModel (cList *l) : QAbstractItemModel(), lst(l) { 0047 } 0048 0049 QModelIndex index (int row, int column, const QModelIndex &parent = QModelIndex()) const override 0050 { 0051 if (!hasIndex(row, column, parent)) 0052 return QModelIndex(); 0053 0054 cListGroup *group = parent.isValid() ? static_cast<cListGroup *>(parent.internalPointer()) : lst->rootGroup(); 0055 0056 cListObject *obj = group->objectAt (row); 0057 if (!obj) return QModelIndex(); 0058 return createIndex (row, column, (void *) obj); 0059 } 0060 0061 QModelIndex indexOf (const cListObject *obj) const 0062 { 0063 if (obj == lst->rootGroup()) return QModelIndex(); 0064 if (!obj) return QModelIndex(); 0065 return createIndex (obj->positionInGroup(), 0, (void *) obj); 0066 } 0067 0068 QModelIndex parent (const QModelIndex &index) const override 0069 { 0070 if (!index.isValid()) return QModelIndex(); 0071 cListObject *obj = static_cast<cListObject *>(index.internalPointer()); 0072 cListGroup *group = obj->parentGroup (); 0073 if ((!group) || (group == lst->rootGroup())) // root or top-level item 0074 return QModelIndex(); 0075 return createIndex (group->positionInGroup(), 0, (void *) group); 0076 } 0077 0078 int columnCount (const QModelIndex &) const override 0079 { 0080 return 1; // we have one column 0081 } 0082 0083 int rowCount (const QModelIndex &parent = QModelIndex()) const override 0084 { 0085 if (parent.column() > 0) return 0; // we only have a single column 0086 0087 cListObject *obj = parent.isValid() ? static_cast<cListObject *>(parent.internalPointer()) : lst->rootGroup(); 0088 if (!obj) return 0; 0089 if (!obj->isGroup()) return 0; 0090 return static_cast<cListGroup *>(obj)->objectCount(); 0091 } 0092 0093 QVariant data ( const QModelIndex &index, int role = Qt::DisplayRole) const override 0094 { 0095 cListObject *obj = index.isValid() ? static_cast<cListObject *>(index.internalPointer()) : lst->rootGroup(); 0096 0097 // DisplayRole - the visible name 0098 if (role == Qt::DisplayRole) 0099 return obj->visibleName(); 0100 0101 if (role == Qt::UserRole) { 0102 // here we return the object/group 0103 return QVariant::fromValue (obj); 0104 } 0105 0106 if (role == Qt::DecorationRole) { 0107 return obj->enabled() ? QIcon() : QIcon::fromTheme("dialog-cancel"); 0108 } 0109 0110 if (role == Qt::FontRole) { 0111 QFont bold; 0112 bold.setBold (true); 0113 return obj->isGroup() ? bold : QVariant(); 0114 } 0115 0116 return QVariant(); 0117 } 0118 0119 Qt::ItemFlags flags (const QModelIndex &index) const override 0120 { 0121 Qt::ItemFlags res = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; 0122 // anything can be dragged, only groups accept drops 0123 res |= Qt::ItemIsDragEnabled; 0124 cListObject *obj = index.isValid() ? static_cast<cListObject *>(index.internalPointer()) : lst->rootGroup(); 0125 if (obj->isGroup()) res |= Qt::ItemIsDropEnabled; 0126 return res; 0127 } 0128 0129 void startAddRows (const QModelIndex &parent, int from, int to) 0130 { 0131 //emit layoutAboutToBeChanged(); 0132 beginInsertRows (parent, from, to); 0133 } 0134 0135 void addedRows () 0136 { 0137 endInsertRows (); 0138 //emit layoutChanged(); 0139 } 0140 0141 void startRemoveRows (const QModelIndex &parent, int from, int to) 0142 { 0143 emit layoutAboutToBeChanged(); 0144 beginRemoveRows (parent, from, to); 0145 } 0146 0147 void removedRows () 0148 { 0149 endRemoveRows (); 0150 emit layoutChanged(); 0151 } 0152 0153 void notifyChanged (const QModelIndex &parent, int from, int to) 0154 { 0155 emit dataChanged (index (from, 0, parent), index (to, 0, parent)); 0156 } 0157 0158 // drag and drop 0159 Qt::DropActions supportedDropActions () const override 0160 { 0161 return Qt::MoveAction; 0162 } 0163 0164 QStringList mimeTypes () const override 0165 { 0166 QStringList types; 0167 types << "application/kmuddy.object.info"; 0168 return types; 0169 } 0170 0171 QMimeData *mimeData (const QModelIndexList &indexes) const override 0172 { 0173 QMimeData *mimeData = new QMimeData(); 0174 QByteArray encodedData; 0175 QDataStream stream (&encodedData, QIODevice::WriteOnly); 0176 0177 cListManager *lm = cListManager::self(); 0178 for (QModelIndexList::const_iterator it = indexes.begin(); it != indexes.end(); ++it) { 0179 if (!(*it).isValid()) continue; 0180 cListObject *obj = static_cast<cListObject *>((*it).internalPointer()); 0181 int id = lm->objectId (obj); 0182 if (!id) continue; 0183 stream << id; 0184 } 0185 0186 mimeData->setData ("application/kmuddy.object.info", encodedData); 0187 return mimeData; 0188 } 0189 0190 bool dropMimeData (const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override 0191 { 0192 if (action == Qt::IgnoreAction) return true; 0193 if (!data->hasFormat("application/kmuddy.object.info")) return false; 0194 if (column > 0) return false; 0195 0196 cListObject *grp = parent.isValid() ? static_cast<cListObject *>(parent.internalPointer()) : lst->rootGroup(); 0197 if (!grp->isGroup()) { // we dropped onto an end-object - can this ever happen ? 0198 // will insert after this item 0199 row = grp->positionInGroup() + 1; 0200 grp = grp->parentGroup (); 0201 } 0202 cListGroup *group = (cListGroup *) grp; 0203 QByteArray encodedData = data->data ("application/kmuddy.object.info"); 0204 QDataStream stream (&encodedData, QIODevice::ReadOnly); 0205 0206 cListManager *lm = cListManager::self(); 0207 while (!stream.atEnd()) { 0208 // fetch an object ID from the dropped data 0209 int id; 0210 stream >> id; 0211 cListObject *obj = lm->object (id); 0212 // ensure that the object is valid and belongs to the same list 0213 if (!obj) continue; 0214 if (obj->list() != lst) continue; 0215 0216 // alright, we need to move this object to the designated position 0217 // also adjust the designated position if needed, so that the next object is placed correctly 0218 bool sameGroup = (group == obj->parentGroup ()); 0219 if (!sameGroup) lst->addToGroup (group, obj); 0220 // adjust row number so that everything works well when moving within the same group 0221 if (sameGroup && obj->positionInGroup() < row) row--; 0222 if (row >= 0) 0223 group->moveObjectToPosition (obj, row++); 0224 } 0225 return true; 0226 } 0227 0228 private: 0229 cList *lst; 0230 0231 }; 0232 0233 struct cList::Private 0234 { 0235 bool enabled; 0236 QString name; 0237 int sess; 0238 cListModel *model; 0239 map<QString, cListProperty> propertyList; 0240 0241 cListGroup *rootGroup; 0242 map<QString, cListGroup *> groups; 0243 map<QString, cListObject *> namedObjects; 0244 0245 QString lastError; 0246 bool hasError; 0247 }; 0248 0249 cList::cList (const QString &name) 0250 { 0251 d = new Private; 0252 d->enabled = true; 0253 d->name = name; 0254 d->model = new cListModel (this); 0255 d->rootGroup = nullptr; 0256 d->sess = 0; 0257 d->hasError = false; 0258 } 0259 0260 // initialize the root group 0261 // this cannot be in the constructor, because the cListObject constructor 0262 // is calling our pure virtual method, which can only be done when the object 0263 // has been fully constructed 0264 void cList::initRootGroup () 0265 { 0266 if (d->rootGroup) return; 0267 d->rootGroup = new cListGroup (this); 0268 d->rootGroup->setName ("Root"); 0269 d->groups["Root"] = d->rootGroup; 0270 } 0271 0272 cList::~cList () 0273 { 0274 // clear (); // not called here, so that we can still use the inherited list in cListObject-derived class destructors 0275 delete d->rootGroup; 0276 delete d->model; 0277 delete d; 0278 } 0279 0280 void cList::setSession (int sess) 0281 { 0282 d->sess = sess; 0283 } 0284 0285 int cList::session () 0286 { 0287 return d->sess; 0288 } 0289 0290 cListGroup *cList::newGroup () 0291 { 0292 return new cListGroup (this); 0293 } 0294 0295 QString cList::name () 0296 { 0297 return d->name; 0298 } 0299 0300 const std::map<QString, cListProperty> &cList::getPropertyList () 0301 { 0302 return d->propertyList; 0303 } 0304 0305 int cList::defaultIntValue (const QString &name) 0306 { 0307 if (!d->propertyList.count (name)) return 0; 0308 if (d->propertyList[name].type != Int) return 0; 0309 return d->propertyList[name].defIntValue; 0310 } 0311 0312 QString cList::defaultStrValue (const QString &name) 0313 { 0314 if (!d->propertyList.count (name)) return QString(); 0315 if (d->propertyList[name].type != String) return QString(); 0316 return d->propertyList[name].defStrValue; 0317 } 0318 0319 bool cList::defaultBoolValue (const QString &name) 0320 { 0321 if (!d->propertyList.count (name)) return false; 0322 if (d->propertyList[name].type != Bool) return false; 0323 return d->propertyList[name].defBoolValue; 0324 } 0325 0326 bool cList::enabled () 0327 { 0328 return d->enabled; 0329 } 0330 0331 void cList::setEnabled (bool en) 0332 { 0333 d->enabled = en; 0334 } 0335 0336 cListGroup *cList::rootGroup () 0337 { 0338 return d->rootGroup; 0339 } 0340 0341 cListGroup *cList::group (const QString &name) 0342 { 0343 if (d->groups.count (name)) 0344 return d->groups[name]; 0345 return nullptr; 0346 } 0347 0348 cListGroup *cList::addGroup (cListGroup *parent, const QString &name) 0349 { 0350 cListGroup *g = group (name); 0351 if (g) return g; // group already exists 0352 g = newGroup (); 0353 g->setName (name); 0354 g->setParentGroup (parent); 0355 d->groups[name] = g; 0356 return g; 0357 } 0358 0359 bool cList::renameGroup (cListGroup *group, const QString &newName) 0360 { 0361 if (d->groups.count (newName)) return false; 0362 if (group == d->rootGroup) return false; 0363 d->groups.erase (group->name()); 0364 group->setName (newName); 0365 d->groups[newName] = group; 0366 return true; 0367 } 0368 0369 void cList::removeGroup (cListGroup *group) 0370 { 0371 if (group == d->rootGroup) return; // the root group cannot be removed 0372 0373 // reparent all childs to the parent group 0374 cListGroup *parent = group->parentGroup (); 0375 const std::list<cListObject *> *objects = group->objectList (); 0376 std::list<cListObject *> moveList = *objects; // make a copy of the list 0377 // the copy is made so that the iterator doesn't get invalidated 0378 std::list<cListObject *>::iterator it; 0379 for (it = moveList.begin(); it != moveList.end(); ++it) 0380 (*it)->setParentGroup (parent); 0381 0382 // the group is empty now - remove 0383 d->groups.erase (group->name()); 0384 delete group; 0385 } 0386 0387 void cList::addToGroup (cListGroup *group, cListObject *item) 0388 { 0389 if (item == d->rootGroup) return; 0390 item->setParentGroup (group); 0391 } 0392 0393 bool cList::setObjectName (cListObject *obj, const QString &name) 0394 { 0395 if (obj->list() != this) return false; 0396 if (d->namedObjects.count (name)) return false; // name already exists 0397 if (obj->isGroup()) return false; // not to be used on groups 0398 if (!obj->name().isEmpty()) 0399 d->namedObjects.erase (obj->name()); 0400 obj->setName (name); 0401 if (!name.isEmpty()) 0402 d->namedObjects[name] = obj; 0403 return true; 0404 } 0405 0406 cListObject *cList::getObject (const QString &name) 0407 { 0408 if (d->namedObjects.count (name)) 0409 return d->namedObjects[name]; 0410 return nullptr; 0411 } 0412 0413 void cList::deleteObject (cListObject *obj) 0414 { 0415 if (obj->list() != this) return; // must be one of ours 0416 if (obj->isGroup()) return; // must not be a group 0417 delete obj; 0418 } 0419 0420 void cList::clear () 0421 { 0422 // first, remove all the groups; this moves all the objects to the root group 0423 // list copying is done to prevent iterator invalidation 0424 list<cListGroup *> g; 0425 std::map<QString, cListGroup *>::iterator it; 0426 for (it = d->groups.begin(); it != d->groups.end(); ++it) 0427 g.push_back (it->second); 0428 std::list<cListGroup *>::iterator itl; 0429 for (itl = g.begin(); itl != g.end(); ++itl) 0430 removeGroup (*itl); 0431 0432 // second, delete all the objects 0433 const std::list<cListObject *> *objects = d->rootGroup->objectList (); 0434 std::list<cListObject *> moveList = *objects; 0435 std::list<cListObject *>::iterator ito; 0436 for (ito = moveList.begin(); ito != moveList.end(); ++ito) 0437 deleteObject (*ito); 0438 0439 // finally, clear the list of named objects 0440 d->namedObjects.clear (); 0441 } 0442 0443 void cList::traverse (int traversalType) 0444 { 0445 if (!enabled()) return; // list must be enabled 0446 d->rootGroup->traverse (traversalType); 0447 } 0448 0449 void cList::load (QXmlStreamReader *reader) 0450 { 0451 // remove all existing elements 0452 clear (); 0453 d->hasError = false; 0454 0455 reader->readNext (); // read the document start 0456 reader->readNext (); 0457 if (reader->isStartElement ()) 0458 if (reader->name() == "list") 0459 if (reader->attributes().value ("version") == "1.0") { 0460 // all is well, we can start loading the list 0461 // so read the root group 0462 do { 0463 reader->readNext (); 0464 } while (!(reader->isStartElement () || reader->atEnd())); 0465 if (reader->isStartElement () && (reader->name() == "group")) 0466 d->rootGroup->load (reader); 0467 else 0468 reader->raiseError (i18n ("This file does not contain the root group, and therefore cannot be loaded.")); 0469 } 0470 else 0471 reader->raiseError (i18n ("This file was created by a newer version of KMuddy, and this version is unable to open it.")); 0472 else 0473 reader->raiseError (i18n ("This is not a KMuddy object file.")); 0474 else if (!reader->hasError()) 0475 reader->raiseError (i18n ("This file is corrupted.")); 0476 0477 if (reader->hasError()) { 0478 d->hasError = true; 0479 d->lastError = i18n ("Error at line %1, column %2: %3", 0480 QString::number (reader->lineNumber()), 0481 QString::number (reader->columnNumber()), 0482 reader->errorString()); 0483 } 0484 0485 listLoaded (); // the list is loaded now 0486 } 0487 0488 void cList::save (QXmlStreamWriter *writer) 0489 { 0490 writer->setAutoFormatting (true); // make the generated XML more readable 0491 writer->writeStartDocument (); 0492 0493 writer->writeStartElement ("list"); 0494 writer->writeAttribute ("version", "1.0"); 0495 0496 d->rootGroup->save (writer); 0497 0498 writer->writeEndElement (); // end the list element 0499 writer->writeEndDocument (); 0500 } 0501 0502 bool cList::hasError () 0503 { 0504 return d->hasError; 0505 } 0506 0507 void cList::clearError () 0508 { 0509 d->hasError = false; 0510 } 0511 0512 const QString cList::lastError () 0513 { 0514 return d->lastError; 0515 } 0516 0517 void cList::addProperty (const cListProperty &prop) 0518 { 0519 d->propertyList[prop.name] = prop; 0520 } 0521 0522 void cList::addIntProperty (const QString &name, const QString &desc, int defaultValue) 0523 { 0524 cListProperty p; 0525 p.name = name; 0526 p.desc = desc; 0527 p.type = Int; 0528 p.defIntValue = defaultValue; 0529 addProperty (p); 0530 } 0531 0532 void cList::addStringProperty (const QString &name, const QString &desc, QString defaultValue) 0533 { 0534 cListProperty p; 0535 p.name = name; 0536 p.desc = desc; 0537 p.type = String; 0538 p.defStrValue = defaultValue; 0539 addProperty (p); 0540 } 0541 0542 void cList::addBoolProperty (const QString &name, const QString &desc, bool defaultValue) 0543 { 0544 cListProperty p; 0545 p.name = name; 0546 p.desc = desc; 0547 p.type = Bool; 0548 p.defBoolValue = defaultValue; 0549 addProperty (p); 0550 } 0551 0552 void cList::addObject (cListObject *obj) 0553 { 0554 obj->updateVisibleName (); 0555 } 0556 0557 void cList::removeObject (cListObject *obj) 0558 { 0559 if (!obj->name().isEmpty()) 0560 d->namedObjects.erase (obj->name()); 0561 } 0562 0563 QAbstractItemModel *cList::model () 0564 { 0565 return d->model; 0566 } 0567 0568 cListObject *cList::objectAt (const QModelIndex &index) 0569 { 0570 QVariant data = d->model->data (index, Qt::UserRole); 0571 cListObject *obj = data.value<cListObject *>(); 0572 return obj; 0573 } 0574 0575 QModelIndex cList::indexOf (const cListObject *obj) 0576 { 0577 return d->model->indexOf (obj); 0578 } 0579 0580 void cList::notifyAdding (cListGroup *group, int pos) 0581 { 0582 d->model->startAddRows (d->model->indexOf (group), pos, pos); 0583 } 0584 0585 void cList::addDone () 0586 { 0587 d->model->addedRows(); 0588 } 0589 0590 void cList::notifyRemoving (cListObject *obj) 0591 { 0592 cListGroup *group = obj->parentGroup(); 0593 int pos = obj->positionInGroup(); 0594 d->model->startRemoveRows (d->model->indexOf (group), pos, pos); 0595 } 0596 0597 void cList::removeDone () 0598 { 0599 d->model->removedRows(); 0600 } 0601 0602 void cList::notifyChanged (cListObject *obj) 0603 { 0604 cListGroup *group = obj->parentGroup(); 0605 int pos = obj->positionInGroup(); 0606 d->model->notifyChanged (d->model->indexOf (group), pos, pos); 0607 } 0608