File indexing completed on 2024-05-12 15:58:28

0001 /*
0002  * SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_node.h"
0008 
0009 #include <QList>
0010 #include <QReadWriteLock>
0011 #include <QReadLocker>
0012 #include <QWriteLocker>
0013 #include <QPainterPath>
0014 #include <QRect>
0015 #include <QCoreApplication>
0016 
0017 #include <KoProperties.h>
0018 
0019 #include "kis_global.h"
0020 #include "kis_node_graph_listener.h"
0021 #include "kis_node_visitor.h"
0022 #include "kis_processing_visitor.h"
0023 #include "kis_node_progress_proxy.h"
0024 #include "kis_busy_progress_indicator.h"
0025 
0026 #include "kis_clone_layer.h"
0027 
0028 #include "kis_time_span.h"
0029 
0030 #include "kis_safe_read_list.h"
0031 typedef KisSafeReadList<KisNodeSP> KisSafeReadNodeList;
0032 
0033 #include "kis_abstract_projection_plane.h"
0034 #include "kis_projection_leaf.h"
0035 #include "kis_undo_adapter.h"
0036 #include "kis_keyframe_channel.h"
0037 #include "kis_image.h"
0038 #include "kis_layer_utils.h"
0039 #include "KisRegion.h"
0040 
0041 /**
0042  *The link between KisProjection and KisImageUpdater
0043  *uses queued signals with an argument of KisNodeSP type,
0044  *so we should register it beforehand
0045  */
0046 struct KisNodeSPStaticRegistrar {
0047     KisNodeSPStaticRegistrar() {
0048         qRegisterMetaType<KisNodeSP>("KisNodeSP");
0049     }
0050 };
0051 static KisNodeSPStaticRegistrar __registrar1;
0052 
0053 struct KisNodeListStaticRegistrar {
0054     KisNodeListStaticRegistrar() {
0055         qRegisterMetaType<KisNodeList>("KisNodeList");
0056     }
0057 };
0058 static KisNodeListStaticRegistrar __registrar2;
0059 
0060 
0061 /**
0062  * Note about "thread safety" of KisNode
0063  *
0064  * 1) One can *read* any information about node and node graph in any
0065  *    number of threads concurrently. This operation is safe because
0066  *    of the usage of KisSafeReadNodeList and will run concurrently
0067  *    (lock-free).
0068  *
0069  * 2) One can *write* any information into the node or node graph in a
0070  *    single thread only! Changing the graph concurrently is *not*
0071  *    sane and therefore not supported.
0072  *
0073  * 3) One can *read and write* information about the node graph
0074  *    concurrently, given that there is only *one* writer thread and
0075  *    any number of reader threads. Please note that in this case the
0076  *    node's code is just guaranteed *not to crash*, which is ensured
0077  *    by nodeSubgraphLock. You need to ensure the sanity of the data
0078  *    read by the reader threads yourself!
0079  */
0080 
0081 struct Q_DECL_HIDDEN KisNode::Private
0082 {
0083 public:
0084     Private(KisNode *node)
0085             : graphListener(0)
0086             , nodeProgressProxy(0)
0087             , busyProgressIndicator(0)
0088             , projectionLeaf(new KisProjectionLeaf(node))
0089     {
0090     }
0091 
0092     KisNodeWSP parent;
0093     KisNodeGraphListener *graphListener;
0094     KisSafeReadNodeList nodes;
0095     KisNodeProgressProxy *nodeProgressProxy;
0096     KisBusyProgressIndicator *busyProgressIndicator;
0097     QReadWriteLock nodeSubgraphLock;
0098 
0099     KisProjectionLeafSP projectionLeaf;
0100 
0101     const KisNode* findSymmetricClone(const KisNode *srcRoot,
0102                                       const KisNode *dstRoot,
0103                                       const KisNode *srcTarget);
0104     void processDuplicatedClones(const KisNode *srcDuplicationRoot,
0105                                  const KisNode *dstDuplicationRoot,
0106                                  KisNode *node);
0107 };
0108 
0109 /**
0110  * Finds the layer in \p dstRoot subtree, which has the same path as
0111  * \p srcTarget has in \p srcRoot
0112  */
0113 const KisNode* KisNode::Private::findSymmetricClone(const KisNode *srcRoot,
0114                                                     const KisNode *dstRoot,
0115                                                     const KisNode *srcTarget)
0116 {
0117     if (srcRoot == srcTarget) return dstRoot;
0118 
0119     KisSafeReadNodeList::const_iterator srcIter = srcRoot->m_d->nodes.constBegin();
0120     KisSafeReadNodeList::const_iterator dstIter = dstRoot->m_d->nodes.constBegin();
0121 
0122     for (; srcIter != srcRoot->m_d->nodes.constEnd(); srcIter++, dstIter++) {
0123 
0124         KIS_ASSERT_RECOVER_RETURN_VALUE((srcIter != srcRoot->m_d->nodes.constEnd()) ==
0125                                         (dstIter != dstRoot->m_d->nodes.constEnd()), 0);
0126 
0127         const KisNode *node = findSymmetricClone(srcIter->data(), dstIter->data(), srcTarget);
0128         if (node) return node;
0129 
0130     }
0131 
0132     return 0;
0133 }
0134 
0135 /**
0136  * This function walks through a subtrees of old and new layers and
0137  * searches for clone layers. For each clone layer it checks whether
0138  * its copyFrom() lays inside the old subtree, and if it is so resets
0139  * it to the corresponding layer in the new subtree.
0140  *
0141  * That is needed when the user duplicates a group layer with all its
0142  * layer subtree. In such a case all the "internal" clones must stay
0143  * "internal" and not point to the layers of the older group.
0144  */
0145 void KisNode::Private::processDuplicatedClones(const KisNode *srcDuplicationRoot,
0146                                                const KisNode *dstDuplicationRoot,
0147                                                KisNode *node)
0148 {
0149     if (KisCloneLayer *clone = dynamic_cast<KisCloneLayer*>(node)) {
0150         KIS_ASSERT_RECOVER_RETURN(clone->copyFrom());
0151         const KisNode *newCopyFrom = findSymmetricClone(srcDuplicationRoot,
0152                                                         dstDuplicationRoot,
0153                                                         clone->copyFrom());
0154 
0155         if (newCopyFrom) {
0156             KisLayer *newCopyFromLayer = qobject_cast<KisLayer*>(const_cast<KisNode*>(newCopyFrom));
0157             KIS_ASSERT_RECOVER_RETURN(newCopyFromLayer);
0158 
0159             clone->setCopyFrom(newCopyFromLayer);
0160         }
0161     }
0162 
0163     KisSafeReadNodeList::const_iterator iter;
0164     FOREACH_SAFE(iter, node->m_d->nodes) {
0165         KisNode *child = const_cast<KisNode*>((*iter).data());
0166         processDuplicatedClones(srcDuplicationRoot, dstDuplicationRoot, child);
0167     }
0168 }
0169 
0170 KisNode::KisNode(KisImageWSP image)
0171         : KisBaseNode(image),
0172           m_d(new Private(this))
0173 {
0174     m_d->parent = 0;
0175     m_d->graphListener = 0;
0176     moveToThread(qApp->thread());
0177 }
0178 
0179 KisNode::KisNode(const KisNode & rhs)
0180         : KisBaseNode(rhs)
0181         , m_d(new Private(this))
0182 {
0183     m_d->parent = 0;
0184     m_d->graphListener = 0;
0185     moveToThread(qApp->thread());
0186 
0187     // NOTE: the nodes are not supposed to be added/removed while
0188     // creation of another node, so we do *no* locking here!
0189 
0190     KisSafeReadNodeList::const_iterator iter;
0191     FOREACH_SAFE(iter, rhs.m_d->nodes) {
0192         KisNodeSP child = (*iter)->clone();
0193         child->createNodeProgressProxy();
0194         m_d->nodes.append(child);
0195         child->setParent(this);
0196     }
0197 
0198     m_d->processDuplicatedClones(&rhs, this, this);
0199 }
0200 
0201 KisNode::~KisNode()
0202 {
0203     if (m_d->busyProgressIndicator) {
0204         m_d->busyProgressIndicator->prepareDestroying();
0205         m_d->busyProgressIndicator->deleteLater();
0206     }
0207 
0208     if (m_d->nodeProgressProxy) {
0209         m_d->nodeProgressProxy->prepareDestroying();
0210         m_d->nodeProgressProxy->deleteLater();
0211     }
0212 
0213     {
0214         QWriteLocker l(&m_d->nodeSubgraphLock);
0215         m_d->nodes.clear();
0216     }
0217 
0218     delete m_d;
0219 }
0220 
0221 QRect KisNode::needRect(const QRect &rect, PositionToFilthy pos) const
0222 {
0223     Q_UNUSED(pos);
0224     return rect;
0225 }
0226 
0227 QRect KisNode::changeRect(const QRect &rect, PositionToFilthy pos) const
0228 {
0229     Q_UNUSED(pos);
0230     return rect;
0231 }
0232 
0233 QRect KisNode::accessRect(const QRect &rect, PositionToFilthy pos) const
0234 {
0235     Q_UNUSED(pos);
0236     return rect;
0237 }
0238 
0239 void KisNode::childNodeChanged(KisNodeSP /*changedChildNode*/)
0240 {
0241 }
0242 
0243 KisAbstractProjectionPlaneSP KisNode::projectionPlane() const
0244 {
0245     KIS_ASSERT_RECOVER_NOOP(0 && "KisNode::projectionPlane() is not defined!");
0246     static KisAbstractProjectionPlaneSP plane =
0247         toQShared(new KisDumbProjectionPlane());
0248 
0249     return plane;
0250 }
0251 
0252 KisProjectionLeafSP KisNode::projectionLeaf() const
0253 {
0254     return m_d->projectionLeaf;
0255 }
0256 
0257 void KisNode::setImage(KisImageWSP newImage)
0258 {
0259     KisBaseNode::setImage(newImage);
0260 
0261     KisNodeSP node = firstChild();
0262     while (node) {
0263         KisLayerUtils::recursiveApplyNodes(node,
0264                                            [newImage] (KisNodeSP node) {
0265                                                node->setImage(newImage);
0266                                            });
0267 
0268         node = node->nextSibling();
0269     }
0270 }
0271 
0272 bool KisNode::accept(KisNodeVisitor &v)
0273 {
0274     return v.visit(this);
0275 }
0276 
0277 void KisNode::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
0278 {
0279     visitor.visit(this, undoAdapter);
0280 }
0281 
0282 int KisNode::graphSequenceNumber() const
0283 {
0284     return m_d->graphListener ? m_d->graphListener->graphSequenceNumber() : -1;
0285 }
0286 
0287 KisNodeGraphListener *KisNode::graphListener() const
0288 {
0289     return m_d->graphListener;
0290 }
0291 
0292 void KisNode::setGraphListener(KisNodeGraphListener *graphListener)
0293 {
0294     m_d->graphListener = graphListener;
0295 
0296     QReadLocker l(&m_d->nodeSubgraphLock);
0297     KisSafeReadNodeList::const_iterator iter;
0298     FOREACH_SAFE(iter, m_d->nodes) {
0299         KisNodeSP child = (*iter);
0300         child->setGraphListener(graphListener);
0301     }
0302 }
0303 
0304 void KisNode::setParent(KisNodeWSP parent)
0305 {
0306     QWriteLocker l(&m_d->nodeSubgraphLock);
0307     m_d->parent = parent;
0308 }
0309 
0310 KisNodeSP KisNode::parent() const
0311 {
0312     QReadLocker l(&m_d->nodeSubgraphLock);
0313     return m_d->parent.isValid() ? KisNodeSP(m_d->parent) : KisNodeSP();
0314 }
0315 
0316 KisBaseNodeSP KisNode::parentCallback() const
0317 {
0318     return parent();
0319 }
0320 
0321 void KisNode::notifyParentVisibilityChanged(bool value)
0322 {
0323     QReadLocker l(&m_d->nodeSubgraphLock);
0324 
0325     KisSafeReadNodeList::const_iterator iter;
0326     FOREACH_SAFE(iter, m_d->nodes) {
0327         KisNodeSP child = (*iter);
0328         child->notifyParentVisibilityChanged(value);
0329     }
0330 }
0331 
0332 void KisNode::baseNodeChangedCallback()
0333 {
0334     if(m_d->graphListener) {
0335         m_d->graphListener->nodeChanged(this);
0336         emit sigNodeChangedInternal();
0337     }
0338 }
0339 
0340 void KisNode::baseNodeInvalidateAllFramesCallback()
0341 {
0342     if(m_d->graphListener) {
0343         m_d->graphListener->invalidateAllFrames();
0344     }
0345 }
0346 
0347 void KisNode::baseNodeCollapsedChangedCallback()
0348 {
0349     if(m_d->graphListener) {
0350         m_d->graphListener->nodeCollapsedChanged(this);
0351     }
0352 }
0353 
0354 void KisNode::addKeyframeChannel(KisKeyframeChannel *channel)
0355 {
0356     channel->setNode(this);
0357     KisBaseNode::addKeyframeChannel(channel);
0358 
0359     if (m_d->graphListener) {
0360         m_d->graphListener->keyframeChannelHasBeenAdded(this, channel);
0361     }
0362 }
0363 
0364 KisNodeSP KisNode::firstChild() const
0365 {
0366     QReadLocker l(&m_d->nodeSubgraphLock);
0367     return !m_d->nodes.isEmpty() ? m_d->nodes.first() : 0;
0368 }
0369 
0370 KisNodeSP KisNode::lastChild() const
0371 {
0372     QReadLocker l(&m_d->nodeSubgraphLock);
0373     return !m_d->nodes.isEmpty() ? m_d->nodes.last() : 0;
0374 }
0375 
0376 KisNodeSP KisNode::prevChildImpl(KisNodeSP child)
0377 {
0378     /**
0379      * Warning: mind locking policy!
0380      *
0381      * The graph locks must be *always* taken in descending
0382      * order. That is if you want to (or it implicitly happens that
0383      * you) take a lock of a parent and a chil, you must first take
0384      * the lock of a parent, and only after that ask a child to do the
0385      * same.  Otherwise you'll get a deadlock.
0386      */
0387 
0388     QReadLocker l(&m_d->nodeSubgraphLock);
0389 
0390     int i = m_d->nodes.indexOf(child) - 1;
0391     return i >= 0 ? m_d->nodes.at(i) : 0;
0392 }
0393 
0394 KisNodeSP KisNode::nextChildImpl(KisNodeSP child)
0395 {
0396     /**
0397      * See a comment in KisNode::prevChildImpl()
0398      */
0399     QReadLocker l(&m_d->nodeSubgraphLock);
0400 
0401     int i = m_d->nodes.indexOf(child) + 1;
0402     return i > 0 && i < m_d->nodes.size() ? m_d->nodes.at(i) : 0;
0403 }
0404 
0405 KisNodeSP KisNode::prevSibling() const
0406 {
0407     KisNodeSP parentNode = parent();
0408     return parentNode ? parentNode->prevChildImpl(const_cast<KisNode*>(this)) : 0;
0409 }
0410 
0411 KisNodeSP KisNode::nextSibling() const
0412 {
0413     KisNodeSP parentNode = parent();
0414     return parentNode ? parentNode->nextChildImpl(const_cast<KisNode*>(this)) : 0;
0415 }
0416 
0417 quint32 KisNode::childCount() const
0418 {
0419     QReadLocker l(&m_d->nodeSubgraphLock);
0420     return m_d->nodes.size();
0421 }
0422 
0423 
0424 KisNodeSP KisNode::at(quint32 index) const
0425 {
0426     QReadLocker l(&m_d->nodeSubgraphLock);
0427 
0428     if (!m_d->nodes.isEmpty() && index < (quint32)m_d->nodes.size()) {
0429         return m_d->nodes.at(index);
0430     }
0431 
0432     return 0;
0433 }
0434 
0435 int KisNode::index(const KisNodeSP node) const
0436 {
0437     QReadLocker l(&m_d->nodeSubgraphLock);
0438 
0439     return m_d->nodes.indexOf(node);
0440 }
0441 
0442 QList<KisNodeSP> KisNode::childNodes(const QStringList & nodeTypes, const KoProperties & properties) const
0443 {
0444     QReadLocker l(&m_d->nodeSubgraphLock);
0445 
0446     QList<KisNodeSP> nodes;
0447 
0448     KisSafeReadNodeList::const_iterator iter;
0449     FOREACH_SAFE(iter, m_d->nodes) {
0450         if (*iter) {
0451             if (properties.isEmpty() || (*iter)->check(properties)) {
0452                 bool rightType = true;
0453 
0454                 if(!nodeTypes.isEmpty()) {
0455                     rightType = false;
0456                     Q_FOREACH (const QString &nodeType,  nodeTypes) {
0457                         if ((*iter)->inherits(nodeType.toLatin1())) {
0458                             rightType = true;
0459                             break;
0460                         }
0461                     }
0462                 }
0463                 if (rightType) {
0464                     nodes.append(*iter);
0465                 }
0466             }
0467         }
0468     }
0469     return nodes;
0470 }
0471 
0472 KisNodeSP KisNode::findChildByName(const QString &name)
0473 {
0474     KisNodeSP child = firstChild();
0475     while (child) {
0476         if (child->name() == name) {
0477             return child;
0478         }
0479         if (child->childCount() > 0) {
0480             KisNodeSP grandChild = child->findChildByName(name);
0481             if (grandChild) {
0482                 return grandChild;
0483             }
0484         }
0485         child = child->nextSibling();
0486     }
0487     return 0;
0488 }
0489 
0490 KisNodeSP KisNode::findChildByUniqueID(const QUuid &uuid)
0491 {
0492     KisNodeSP child = firstChild();
0493     while (child) {
0494         if (child->uuid() == uuid) {
0495             return child;
0496         }
0497         if (child->childCount() > 0) {
0498             KisNodeSP grandChild = child->findChildByUniqueID(uuid);
0499             if (grandChild) {
0500                 return grandChild;
0501             }
0502         }
0503         child = child->nextSibling();
0504     }
0505     return 0;
0506 }
0507 
0508 bool KisNode::add(KisNodeSP newNode, KisNodeSP aboveThis)
0509 {
0510     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(newNode, false);
0511     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!aboveThis || aboveThis->parent().data() == this, false);
0512     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(allowAsChild(newNode), false);
0513     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!newNode->parent(), false);
0514     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(index(newNode) < 0, false);
0515 
0516     int idx = aboveThis ? this->index(aboveThis) + 1 : 0;
0517 
0518     // threoretical race condition may happen here ('idx' may become
0519     // deprecated until the write lock will be held). But we ignore
0520     // it, because it is not supported to add/remove nodes from two
0521     // concurrent threads simultaneously
0522 
0523     if (m_d->graphListener) {
0524         m_d->graphListener->aboutToAddANode(this, idx);
0525     }
0526 
0527     {
0528         QWriteLocker l(&m_d->nodeSubgraphLock);
0529 
0530         newNode->createNodeProgressProxy();
0531 
0532         m_d->nodes.insert(idx, newNode);
0533 
0534         newNode->setParent(this);
0535         newNode->setGraphListener(m_d->graphListener);
0536     }
0537 
0538     newNode->setImage(image());
0539 
0540     if (m_d->graphListener) {
0541         m_d->graphListener->nodeHasBeenAdded(this, idx);
0542     }
0543 
0544     childNodeChanged(newNode);
0545 
0546     return true;
0547 }
0548 
0549 bool KisNode::remove(quint32 index)
0550 {
0551     if (index < childCount()) {
0552         KisNodeSP removedNode = at(index);
0553 
0554         if (m_d->graphListener) {
0555             m_d->graphListener->aboutToRemoveANode(this, index);
0556         }
0557 
0558         removedNode->setImage(0);
0559 
0560         {
0561             QWriteLocker l(&m_d->nodeSubgraphLock);
0562 
0563             removedNode->setGraphListener(0);
0564 
0565             removedNode->setParent(0);   // after calling aboutToRemoveANode or then the model get broken according to TT's modeltest
0566             m_d->nodes.removeAt(index);
0567         }
0568 
0569         if (m_d->graphListener) {
0570             m_d->graphListener->nodeHasBeenRemoved(this, index);
0571         }
0572 
0573         childNodeChanged(removedNode);
0574 
0575         return true;
0576     }
0577     return false;
0578 }
0579 
0580 bool KisNode::remove(KisNodeSP node)
0581 {
0582     return node->parent().data() == this ? remove(index(node)) : false;
0583 }
0584 
0585 KisNodeProgressProxy* KisNode::nodeProgressProxy() const
0586 {
0587     if (m_d->nodeProgressProxy) {
0588         return m_d->nodeProgressProxy;
0589     } else if (parent()) {
0590         return parent()->nodeProgressProxy();
0591     }
0592     return 0;
0593 }
0594 
0595 KisBusyProgressIndicator* KisNode::busyProgressIndicator() const
0596 {
0597     if (m_d->busyProgressIndicator) {
0598         return m_d->busyProgressIndicator;
0599     } else if (parent()) {
0600         return parent()->busyProgressIndicator();
0601     }
0602     return 0;
0603 }
0604 
0605 void KisNode::createNodeProgressProxy()
0606 {
0607     if (!m_d->nodeProgressProxy) {
0608         m_d->nodeProgressProxy = new KisNodeProgressProxy(this);
0609         m_d->busyProgressIndicator = new KisBusyProgressIndicator(m_d->nodeProgressProxy);
0610 
0611         m_d->nodeProgressProxy->moveToThread(this->thread());
0612         m_d->busyProgressIndicator->moveToThread(this->thread());
0613     }
0614 }
0615 
0616 void KisNode::setDirty()
0617 {
0618     setDirty(extent());
0619 }
0620 
0621 void KisNode::setDirty(const QVector<QRect> &rects)
0622 {
0623     if(m_d->graphListener) {
0624         m_d->graphListener->requestProjectionUpdate(this, rects, true);
0625     }
0626 }
0627 
0628 void KisNode::setDirty(const KisRegion &region)
0629 {
0630     setDirty(region.rects());
0631 }
0632 
0633 void KisNode::setDirty(const QRect & rect)
0634 {
0635     setDirty(QVector<QRect>({rect}));
0636 }
0637 
0638 void KisNode::setDirtyDontResetAnimationCache()
0639 {
0640     setDirtyDontResetAnimationCache(QVector<QRect>({extent()}));
0641 }
0642 
0643 void KisNode::setDirtyDontResetAnimationCache(const QRect &rect)
0644 {
0645     setDirtyDontResetAnimationCache(QVector<QRect>({rect}));
0646 }
0647 
0648 void KisNode::setDirtyDontResetAnimationCache(const QVector<QRect> &rects)
0649 {
0650     if(m_d->graphListener) {
0651         m_d->graphListener->requestProjectionUpdate(this, rects, false);
0652     }
0653 }
0654 
0655 void KisNode::invalidateFrames(const KisTimeSpan &range, const QRect &rect)
0656 {
0657     if(m_d->graphListener) {
0658         m_d->graphListener->invalidateFrames(range, rect);
0659     }
0660 }
0661 
0662 void KisNode::handleKeyframeChannelUpdate(const KisTimeSpan &range, const QRect &rect)
0663 {
0664     invalidateFrames(range, rect);
0665 
0666     if (image()) {
0667         KisDefaultBoundsSP bounds(new KisDefaultBounds(image()));
0668 
0669         if (range.contains(bounds->currentTime())) {
0670             setDirty(rect);
0671         }
0672     }
0673 }
0674 
0675 void KisNode::requestTimeSwitch(int time)
0676 {
0677     if(m_d->graphListener) {
0678         m_d->graphListener->requestTimeSwitch(time);
0679     }
0680 }
0681 
0682 void KisNode::syncLodCache()
0683 {
0684     // noop. everything is done by getLodCapableDevices()
0685 }
0686 
0687 KisPaintDeviceList KisNode::getLodCapableDevices() const
0688 {
0689     KisPaintDeviceList list;
0690 
0691     KisPaintDeviceSP device = paintDevice();
0692     if (device) {
0693         list << device;
0694     }
0695 
0696     KisPaintDeviceSP originalDevice = original();
0697     if (originalDevice && originalDevice != device) {
0698         list << originalDevice;
0699     }
0700 
0701     list << projectionPlane()->getLodCapableDevices();
0702 
0703     return list;
0704 }