File indexing completed on 2024-04-21 15:08:21

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