From 943175762a6d10becc8789cc6a9b72b821cf7713 Mon Sep 17 00:00:00 2001 From: Konrad Rosenbaum Date: Sat, 11 Feb 2012 23:06:38 +0100 Subject: [PATCH] add some editing functions to dom model; add docu to dom model --- src/misc/dommodel.cpp | 159 ++++++++++++++++++++++++++++++++++++++++++++---- src/misc/dommodel.h | 76 +++++++++++++++++++++-- 2 files changed, 214 insertions(+), 21 deletions(-) diff --git a/src/misc/dommodel.cpp b/src/misc/dommodel.cpp index 3b287e4..7bad551 100644 --- a/src/misc/dommodel.cpp +++ b/src/misc/dommodel.cpp @@ -54,6 +54,7 @@ class DPTR_CLASS_NAME(MDomItemModel) Private(); int buildCache(const QDomNode&,int p=-1); + void removeNode(int nodeid,bool insideRecursion=false); }; DEFINE_DPTR(MDomItemModel); @@ -160,11 +161,16 @@ QSet< QDomNode::NodeType > MDomItemModel::shownNodeTypes()const void MDomItemModel::showElementProperties(int ma,int mt) { + //ignore if there is no change if(d->elemAttrLen == ma && d->elemTextLen == mt)return; - beginResetModel(); + //send a model reset if this has an actual effect, ie. if we do the rendering + bool b=!d->contentFromNode.contains(Qt::DisplayRole); + if(b)beginResetModel(); + //change params d->elemAttrLen=ma; d->elemTextLen=mt; - endResetModel(); + //tell the view + if(b)endResetModel(); } QDomNode MDomItemModel::node(const QModelIndex& index) const @@ -177,12 +183,30 @@ QDomNode MDomItemModel::node(const QModelIndex& index) const return d->domCache[index.internalId()].node.cloneNode(); } -void MDomItemModel::setNode(const QModelIndex& index, const QDomNode& ) +void MDomItemModel::setNode(const QModelIndex& index, const QDomNode& node) { -#warning implement + //get the node id + if(!index.isValid())return; + int nid=index.internalId(); + if(nid<=0 || !d->domCache.contains(nid))return; + int pid=d->domCache[nid].parent; + if(pid<=0 || !d->domCache.contains(pid))return; + //tell the view it is getting nasty, TODO: find a better way + beginResetModel(); + //replace the node in DOM + QDomNode nnode=d->domCache[pid].node.replaceChild(node.cloneNode(),d->domCache[nid].node); + //add the new node + int nnid=d->buildCache(nnode,pid); + //replace reference + int i=d->domCache[pid].children.indexOf(nid); + d->domCache[pid].children.replace(i,nnid); + //remove the old node (inrecursion==true, so the method does not mess with DOM) + d->removeNode(nid,true); + //tell the view we are finished + endResetModel(); } -int MDomItemModel::columnCount(const QModelIndex& parent) const +int MDomItemModel::columnCount(const QModelIndex&) const { return 1; } @@ -354,33 +378,140 @@ QModelIndex MDomItemModel::parent(const QModelIndex& index) const return createIndex(row,0,pid); } -void MDomItemModel::removeNode(const QModelIndex& ) +void MDomItemModel::removeNode(const QModelIndex& index) { -#warning implement + if(!index.isValid())return; + int nid=index.internalId(); + if(!d->domCache.contains(nid))return; + //find parent index and warn the view + QModelIndex pidx=parent(index); + if(pidx.isValid())beginRemoveRows(pidx,index.row(),index.row()); + else beginResetModel(); + //remove the node + d->removeNode(nid); + //tell the view we are done + if(pidx.isValid())endRemoveRows(); + else endResetModel(); } +void MDomItemModel::Private::removeNode(int nid,bool rec) +{ + //sanity check + if(!domCache.contains(nid))return; + //remove main node from model + if(!rec){ + int pid=domCache[nid].parent; + if(domCache.contains(pid)){ + domCache[pid].node.removeChild(domCache[nid].node); + } + } + //remove children + foreach(int cid,domCache[nid].children) + removeNode(cid,true); + //remove self + domCache.remove(nid); +} + + bool MDomItemModel::removeRows(int row, int count, const QModelIndex& parent) { -#warning implement - return false; + //sanity check + if(row<0 || count<=0)return false; + //find the parent + int pid=-1; + if(parent.isValid())pid=parent.internalId(); + else pid=d->docidx; + if(!d->domCache.contains(pid)) + return false; + //check the parent has those children + if(rowCount(parent)>(row+count)) + return false; + //warn the view + beginRemoveRows(parent,row,row+count-1); + //find the children's IDs + QListcids; + int cnt=0; + foreach(int cid,d->domCache[pid].children){ + if(cnt>=(row+count))break; + if(!d->showType.contains(d->domCache[cid].node.nodeType())) + continue; + if(cnt++>=row)cids<removeNode(cid); + //update view + endRemoveRows(); } -void MDomItemModel::reparentChildNodes(const QModelIndex& ) +void MDomItemModel::reparentNode(const QModelIndex& index, const QDomElement& el) { + //find index and parent + if(!index.isValid())return; + int nid=index.internalId(); + if(!d->domCache.contains(nid))return; + int pid=d->domCache[nid].parent; + if(pid<0 || !d->domCache.contains(pid))return; + //invalidate model, TODO: find a less invasive call + beginResetModel(); + //reparent both + QDomElement nelem=el.cloneNode().toElement(); + int neid=d->buildCache(nelem,pid); + QDomNode sibl=d->domCache[nid].node.previousSibling(); + nelem.appendChild(d->domCache[nid].node); + if(sibl.isNull()) + d->domCache[pid].node.insertBefore(nelem,QDomNode()); + else + d->domCache[pid].node.insertAfter(nelem,sibl); + //correct references + d->domCache[nid].parent=neid; + int oidx=d->domCache[pid].children.indexOf(nid); + d->domCache[pid].children.replace(oidx,neid); + d->domCache[neid].children.append(nid); + //update view + endResetModel(); +} + +void MDomItemModel::reparentNode(const QModelIndex& node, const QModelIndex& newparent, int row) +{ + //get node ids + if(!node.isValid() || !newparent.isValid())return; + int nid=node.internalId(); + int npid=newparent.internalId(); + if(nid<=0 || npid<=0 || !d->domCache.contains(nid) || !d->domCache.contains(npid)) + return; + int opid=d->domCache[nid].parent; + if(opid<=0 || !d->domCache.contains(opid))return; + //get position in new parent + int cnt=0; + QDomNode neigh; + foreach(int cid,d->domCache[npid].children){ + if(!d->showType.contains(d->domCache[cid].node.nodeType())) + continue; + //TODO:this is probably wrong + if(cnt++>row)break; + neigh=d->domCache[cid].node; + } + //TODO:insert node in new parent + //TODO:move logical IDs + //TODO:tell view #warning implement } -void MDomItemModel::reparentNode(const QModelIndex& , const QDomNode& ) +void MDomItemModel::reparentNode(const QModelIndexList& nodes, const QModelIndex& newparent, int row) { #warning implement } -void MDomItemModel::setContentFromNodeCall(int r,MDomModelFunctor f) + +void MDomItemModel::setContentFromNodeCall(int role,MDomModelFunctor f) { + if(role==DomNodeRole)return; + beginResetModel(); if(f) - d->contentFromNode.insert(r,f); + d->contentFromNode.insert(role,f); else - d->contentFromNode.remove(r); + d->contentFromNode.remove(role); + endResetModel(); } QVariant MDomItemModel::headerData(int section, Qt::Orientation orientation, int role) const diff --git a/src/misc/dommodel.h b/src/misc/dommodel.h index 030a348..e465032 100644 --- a/src/misc/dommodel.h +++ b/src/misc/dommodel.h @@ -22,54 +22,116 @@ class QDomNode; class QDomDocument; +/** \brief Special functor that can be used to customize MDomItemModel. +Any static function or lambda that takes a QDomNode as argument and returns a QVariant can be used to specialize MDomItemModel. The type of data returned through the QVariant depends on the role the function is supposed to serve. Only one function/lambda can be registered for each role in any given model instance.*/ typedef std::function MDomModelFunctor; -///This is a specialized model type that shows and allows to manipulate a DOM tree. -///The model holds a copy of the DOM tree, so the original is not changed even if the model changes. -///You can retrieve a current copy of the model's DOM tree by calling domDocument() +/** \brief This is a specialized model type that shows and allows to manipulate a DOM tree. +The model holds a copy of the DOM tree, so the original is not changed even if the model changes. +You can retrieve a current copy of the model's DOM tree by calling domDocument(). +This model can not carry application specific user data. It is only editable through its specialized node methods.*/ class MDomItemModel:public QAbstractItemModel { Q_OBJECT DECLARE_DPTR(d); public: + ///instantiate an empty DOM model MDomItemModel(QObject* parent = 0); - MDomItemModel(const QDomDocument&, QObject* parent = 0); + ///instantiate a DOM model with an initial DOM tree + ///\param xml the model will be fed with a copy of this DOM tree + MDomItemModel(const QDomDocument&xml, QObject* parent = 0); + /**returns a copy of the DOM tree represented by this model, + you can safely manipulate this copy without influencing the model. If you want + to change the model's content use the node methods provided here.*/ virtual QDomDocument domDocument()const; + ///alias for Qt::UserRole that is used to retrieve the DOM node of an index. static const int DomNodeRole=Qt::UserRole; - + + ///always returns 1 - the DOM tree has only one column corresponding to the node virtual int columnCount(const QModelIndex&parent=QModelIndex())const; + ///returns data corresponding to the node represented by index; + ///this method can be customized through setContentFromNodeCall(...) virtual QVariant data(const QModelIndex&index, int role=Qt::DisplayRole)const; + + ///returns an index corresponding to the row, column, and parent node + /// \param row the row beneith the parent node - the row number depends on which node types are shown, only visible node types are counted + /// \param column must be 0, otherwise an invalid index is returned + /// \param parent an invalid node for the root node (QDomDocument) or any valid index returned through index(...) virtual QModelIndex index(int row, int column, const QModelIndex&parent=QModelIndex())const; + ///returns the parent index of a node - if it has a parent virtual QModelIndex parent(const QModelIndex&index)const; + ///returns the amount of visible nodes beneith parent virtual int rowCount(const QModelIndex&parent=QModelIndex())const; + ///returns a set of node types that are configured to be visible in this model virtual QSet shownNodeTypes()const; + ///returns the QDomNode corresponding to index, for the invalid index the QDomDocument is returned virtual QDomNode node(const QModelIndex&index)const; + ///sets header data - it is currently possible to set any header for any role, although only few are actually shown in views bool setHeaderData(int section, Qt::Orientation orientation, const QVariant & value, int role = Qt::EditRole); + ///returns the header data for a specific header position and role QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole)const; public slots: + /** sets the document represented by this model; + the model makes a deep copy of the document, so that the original stays intact even if the model changes. Use domDocument() to retrieve a fresh copy of the models version of the document.*/ virtual void setDomDocument(const QDomDocument&); + ///changes the settings of what kinds of nodes are shown in the model, + ///element nodes cannot be switched off, + ///all model indexes become invalid after a call to this method void showNodeTypes(const QSet&); + ///changes the settings of what kinds of nodes are shown in the model, + ///all model indexes become invalid after a call to this method void addShowNodeType(QDomNode::NodeType); + ///changes the settings of what kinds of nodes are shown in the model, + ///all model indexes become invalid after a call to this method void addShowNodeTypes(const QSet&); + ///changes the settings of what kinds of nodes are shown in the model, + ///all model indexes become invalid after a call to this method void showAllNodeTypes(); + /**changes the way element nodes are displayed, + this method has no effect if you customize Qt::DisplayRole through setContentFromNodeCall(...) + \param maxattr the amount of characters shown for attributes, if this is 0 the model will just show "..." if the element has attributes, if this is <0 the model will ignore attributes + \param maxtext the amount of characters shown for character data (QDomText and QDomCDATASection), if this is 0 the model will just show "..." if the element has text, if this is <0 the model will ignore any text + */ void showElementProperties(int maxattr,int maxtext); + ///replaces the node at the index, you cannot replace the entire document with this function - use setDomDocument for this virtual void setNode(const QModelIndex&index,const QDomNode&); + ///removes the nodes at the given position and their child nodes virtual bool removeRows(int row, int count, const QModelIndex&parent=QModelIndex()); + ///removes the node at the given position and its child nodes virtual void removeNode(const QModelIndex&); - virtual void reparentNode(const QModelIndex&,const QDomNode&); - virtual void reparentChildNodes(const QModelIndex&); + /** places the node at the given index inside the new node and then places the new node at that position - in fact moving the node at index one hierarchy level down + \param index the node to be reparented + \param newnode the node that will replace the node and take it as its new child*/ + virtual void reparentNode(const QModelIndex&index,const QDomElement&newnode); + /** places the node in a new parent that is already inside the model + \param node the node to be reparented + \param newparent the new parent of this node + \param row the position inside the parent where to insert that node, if row is greater than the current size of the parent or if it is negative the node will be appended + this call invalidates the new and old parent index of the node*/ + virtual void reparentNode(const QModelIndex&node,const QModelIndex&newparent,int row=-1); + /** places the nodes in a new parent that is already inside the model + \param nodes the nodes to be reparented + \param newparent the new parent of this node + \param row the position inside the parent where to insert the new nodes - they will be inserted in the order they are contained in the list, if row is greater than the current size of the parent or if it is negative the node will be appended + this call invalidates the new and old parent indexes of the nodes*/ + virtual void reparentNode(const QModelIndexList&nodes,const QModelIndex&newparent,int row=-1); + /**Customize the way the model returns data to the view, per default the model only has code to generate a text representation for Qt::DisplayRole. You can override any role, including Qt::DisplayRole, except for DomNodeRole. The callback takes a const reference to QDomNode as argument and should convert it to a QVariant appropriate for the role. + \param role the role to be customized + \param callback a static function, functor or lambda that takes a node as argument and returns a variant as result, use nullptr to delete a customization + */ void setContentFromNodeCall(int role,MDomModelFunctor callback); + ///resets the model to an invalid/empty QDomDocument virtual void clear(){setDomDocument(QDomDocument());} private: -- 1.7.2.5