• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2011 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "SkView.h"
9 #include "SkCanvas.h"
10 #include "SkDOM.h"
11 
SkSetClearShift(uint32_t bits,bool cond,unsigned shift)12 static inline uint32_t SkSetClearShift(uint32_t bits, bool cond, unsigned shift) {
13     SkASSERT((int)cond == 0 || (int)cond == 1);
14     return (bits & ~(1 << shift)) | ((int)cond << shift);
15 }
16 
17 ////////////////////////////////////////////////////////////////////////
18 
SkView(uint32_t flags)19 SkView::SkView(uint32_t flags) : fFlags(SkToU8(flags)) {
20     fWidth = fHeight = 0;
21     fLoc.set(0, 0);
22     fParent = fFirstChild = fNextSibling = fPrevSibling = nullptr;
23     fMatrix.setIdentity();
24     fContainsFocus = 0;
25 }
26 
~SkView()27 SkView::~SkView() {
28     this->detachAllChildren();
29 }
30 
setFlags(uint32_t flags)31 void SkView::setFlags(uint32_t flags) {
32     SkASSERT((flags & ~kAllFlagMasks) == 0);
33 
34     uint32_t diff = fFlags ^ flags;
35 
36     if (diff & kVisible_Mask)
37         this->inval(nullptr);
38 
39     fFlags = SkToU8(flags);
40 
41     if (diff & kVisible_Mask) {
42         this->inval(nullptr);
43     }
44 }
45 
setVisibleP(bool pred)46 void SkView::setVisibleP(bool pred) {
47     this->setFlags(SkSetClearShift(fFlags, pred, kVisible_Shift));
48 }
49 
setEnabledP(bool pred)50 void SkView::setEnabledP(bool pred) {
51     this->setFlags(SkSetClearShift(fFlags, pred, kEnabled_Shift));
52 }
53 
setFocusableP(bool pred)54 void SkView::setFocusableP(bool pred) {
55     this->setFlags(SkSetClearShift(fFlags, pred, kFocusable_Shift));
56 }
57 
setClipToBounds(bool pred)58 void SkView::setClipToBounds(bool pred) {
59     this->setFlags(SkSetClearShift(fFlags, !pred, kNoClip_Shift));
60 }
61 
setSize(SkScalar width,SkScalar height)62 void SkView::setSize(SkScalar width, SkScalar height) {
63     width = SkMaxScalar(0, width);
64     height = SkMaxScalar(0, height);
65 
66     if (fWidth != width || fHeight != height)
67     {
68         this->inval(nullptr);
69         fWidth = width;
70         fHeight = height;
71         this->inval(nullptr);
72         this->onSizeChange();
73         this->invokeLayout();
74     }
75 }
76 
setLoc(SkScalar x,SkScalar y)77 void SkView::setLoc(SkScalar x, SkScalar y) {
78     if (fLoc.fX != x || fLoc.fY != y) {
79         this->inval(nullptr);
80         fLoc.set(x, y);
81         this->inval(nullptr);
82     }
83 }
84 
offset(SkScalar dx,SkScalar dy)85 void SkView::offset(SkScalar dx, SkScalar dy) {
86     if (dx || dy)
87         this->setLoc(fLoc.fX + dx, fLoc.fY + dy);
88 }
89 
setLocalMatrix(const SkMatrix & matrix)90 void SkView::setLocalMatrix(const SkMatrix& matrix) {
91     this->inval(nullptr);
92     fMatrix = matrix;
93     this->inval(nullptr);
94 }
95 
draw(SkCanvas * canvas)96 void SkView::draw(SkCanvas* canvas) {
97     if (fWidth && fHeight && this->isVisible()) {
98         SkRect    r;
99         r.set(fLoc.fX, fLoc.fY, fLoc.fX + fWidth, fLoc.fY + fHeight);
100         if (this->isClipToBounds() && canvas->quickReject(r)) {
101             return;
102         }
103 
104         SkAutoCanvasRestore    as(canvas, true);
105 
106         if (this->isClipToBounds()) {
107             canvas->clipRect(r);
108         }
109 
110         canvas->translate(fLoc.fX, fLoc.fY);
111         canvas->concat(fMatrix);
112 
113         if (fParent) {
114             fParent->beforeChild(this, canvas);
115         }
116 
117         int sc = canvas->save();
118         this->onDraw(canvas);
119         canvas->restoreToCount(sc);
120 
121         if (fParent) {
122             fParent->afterChild(this, canvas);
123         }
124 
125         B2FIter    iter(this);
126         SkView*    child;
127 
128         SkCanvas* childCanvas = this->beforeChildren(canvas);
129 
130         while ((child = iter.next()) != nullptr)
131             child->draw(childCanvas);
132 
133         this->afterChildren(canvas);
134     }
135 }
136 
inval(SkRect * rect)137 void SkView::inval(SkRect* rect) {
138     SkView*    view = this;
139     SkRect storage;
140 
141     for (;;) {
142         if (!view->isVisible()) {
143             return;
144         }
145         if (view->isClipToBounds()) {
146             SkRect bounds;
147             view->getLocalBounds(&bounds);
148             if (rect && !bounds.intersect(*rect)) {
149                 return;
150             }
151             storage = bounds;
152             rect = &storage;
153         }
154         if (view->handleInval(rect)) {
155             return;
156         }
157 
158         SkView* parent = view->fParent;
159         if (parent == nullptr) {
160             return;
161         }
162 
163         if (rect) {
164             rect->offset(view->fLoc.fX, view->fLoc.fY);
165         }
166         view = parent;
167     }
168 }
169 
170 ////////////////////////////////////////////////////////////////////////////
171 
setFocusView(SkView * fv)172 bool SkView::setFocusView(SkView* fv) {
173     SkView* view = this;
174 
175     do {
176         if (view->onSetFocusView(fv)) {
177             return true;
178         }
179     } while ((view = view->fParent) != nullptr);
180     return false;
181 }
182 
getFocusView() const183 SkView* SkView::getFocusView() const {
184     SkView*         focus = nullptr;
185     const SkView*   view = this;
186     do {
187         if (view->onGetFocusView(&focus)) {
188             break;
189         }
190     } while ((view = view->fParent) != nullptr);
191     return focus;
192 }
193 
hasFocus() const194 bool SkView::hasFocus() const {
195     return this == this->getFocusView();
196 }
197 
acceptFocus()198 bool SkView::acceptFocus() {
199     return this->isFocusable() && this->setFocusView(this);
200 }
201 
202 /*
203     Try to give focus to this view, or its children
204 */
acceptFocus(FocusDirection dir)205 SkView* SkView::acceptFocus(FocusDirection dir) {
206     if (dir == kNext_FocusDirection) {
207         if (this->acceptFocus()) {
208             return this;
209         }
210         B2FIter    iter(this);
211         SkView*    child, *focus;
212         while ((child = iter.next()) != nullptr) {
213             if ((focus = child->acceptFocus(dir)) != nullptr) {
214                 return focus;
215             }
216         }
217     } else { // prev
218         F2BIter    iter(this);
219         SkView*    child, *focus;
220         while ((child = iter.next()) != nullptr) {
221             if ((focus = child->acceptFocus(dir)) != nullptr) {
222                 return focus;
223             }
224         }
225         if (this->acceptFocus()) {
226             return this;
227         }
228     }
229     return nullptr;
230 }
231 
moveFocus(FocusDirection dir)232 SkView* SkView::moveFocus(FocusDirection dir) {
233     SkView* focus = this->getFocusView();
234 
235     if (focus == nullptr) {    // start with the root
236         focus = this;
237         while (focus->fParent) {
238             focus = focus->fParent;
239         }
240     }
241 
242     SkView* child, *parent;
243 
244     if (dir == kNext_FocusDirection) {
245         parent = focus;
246         child = focus->fFirstChild;
247         if (child)
248             goto FIRST_CHILD;
249         else
250             goto NEXT_SIB;
251 
252         do {
253             while (child != parent->fFirstChild) {
254     FIRST_CHILD:
255                 if ((focus = child->acceptFocus(dir)) != nullptr)
256                     return focus;
257                 child = child->fNextSibling;
258             }
259     NEXT_SIB:
260             child = parent->fNextSibling;
261             parent = parent->fParent;
262         } while (parent != nullptr);
263     } else {    // prevfocus
264         parent = focus->fParent;
265         if (parent == nullptr) {    // we're the root
266             return focus->acceptFocus(dir);
267         } else {
268             child = focus;
269             while (parent) {
270                 while (child != parent->fFirstChild) {
271                     child = child->fPrevSibling;
272                     if ((focus = child->acceptFocus(dir)) != nullptr) {
273                         return focus;
274                     }
275                 }
276                 if (parent->acceptFocus()) {
277                     return parent;
278                 }
279                 child = parent;
280                 parent = parent->fParent;
281             }
282         }
283     }
284     return nullptr;
285 }
286 
onFocusChange(bool gainFocusP)287 void SkView::onFocusChange(bool gainFocusP) {
288     this->inval(nullptr);
289 }
290 
291 ////////////////////////////////////////////////////////////////////////////
292 
Click(SkView * target)293 SkView::Click::Click(SkView* target) {
294     SkASSERT(target);
295     fTargetID = target->getSinkID();
296     fType = nullptr;
297     fWeOwnTheType = false;
298     fOwner = nullptr;
299 }
300 
~Click()301 SkView::Click::~Click() {
302     this->resetType();
303 }
304 
resetType()305 void SkView::Click::resetType() {
306     if (fWeOwnTheType) {
307         sk_free(fType);
308         fWeOwnTheType = false;
309     }
310     fType = nullptr;
311 }
312 
isType(const char type[]) const313 bool SkView::Click::isType(const char type[]) const {
314     const char* t = fType;
315 
316     if (type == t) {
317         return true;
318     }
319     if (type == nullptr) {
320         type = "";
321     }
322     if (t == nullptr) {
323         t = "";
324     }
325     return !strcmp(t, type);
326 }
327 
setType(const char type[])328 void SkView::Click::setType(const char type[]) {
329     this->resetType();
330     fType = (char*)type;
331 }
332 
copyType(const char type[])333 void SkView::Click::copyType(const char type[]) {
334     if (fType != type) {
335         this->resetType();
336         if (type) {
337             size_t len = strlen(type) + 1;
338             fType = (char*)sk_malloc_throw(len);
339             memcpy(fType, type, len);
340             fWeOwnTheType = true;
341         }
342     }
343 }
344 
findClickHandler(SkScalar x,SkScalar y,unsigned modi)345 SkView::Click* SkView::findClickHandler(SkScalar x, SkScalar y, unsigned modi) {
346     if (x < 0 || y < 0 || x >= fWidth || y >= fHeight) {
347         return nullptr;
348     }
349 
350     if (this->onSendClickToChildren(x, y, modi)) {
351         F2BIter    iter(this);
352         SkView*    child;
353 
354         while ((child = iter.next()) != nullptr) {
355             SkPoint p;
356 #if 0
357             if (!child->globalToLocal(x, y, &p)) {
358                 continue;
359             }
360 #else
361             // the above seems broken, so just respecting fLoc for now <reed>
362             p.set(x - child->fLoc.x(), y - child->fLoc.y());
363 #endif
364 
365             Click* click = child->findClickHandler(p.fX, p.fY, modi);
366 
367             if (click) {
368                 return click;
369             }
370         }
371     }
372 
373     return this->onFindClickHandler(x, y, modi);
374 }
375 
DoClickDown(Click * click,int x,int y,unsigned modi)376 void SkView::DoClickDown(Click* click, int x, int y, unsigned modi) {
377     SkASSERT(click);
378 
379     SkView* target = (SkView*)SkEventSink::FindSink(click->fTargetID);
380     if (nullptr == target) {
381         return;
382     }
383 
384     click->fIOrig.set(x, y);
385     click->fICurr = click->fIPrev = click->fIOrig;
386 
387     click->fOrig.iset(x, y);
388     if (!target->globalToLocal(&click->fOrig)) {
389         // no history to let us recover from this failure
390         return;
391     }
392     click->fPrev = click->fCurr = click->fOrig;
393 
394     click->fState = Click::kDown_State;
395     click->fModifierKeys = modi;
396     target->onClick(click);
397 }
398 
DoClickMoved(Click * click,int x,int y,unsigned modi)399 void SkView::DoClickMoved(Click* click, int x, int y, unsigned modi) {
400     SkASSERT(click);
401 
402     SkView* target = (SkView*)SkEventSink::FindSink(click->fTargetID);
403     if (nullptr == target) {
404         return;
405     }
406 
407     click->fIPrev = click->fICurr;
408     click->fICurr.set(x, y);
409 
410     click->fPrev = click->fCurr;
411     click->fCurr.iset(x, y);
412     if (!target->globalToLocal(&click->fCurr)) {
413         // on failure pretend the mouse didn't move
414         click->fCurr = click->fPrev;
415     }
416 
417     click->fState = Click::kMoved_State;
418     click->fModifierKeys = modi;
419     target->onClick(click);
420 }
421 
DoClickUp(Click * click,int x,int y,unsigned modi)422 void SkView::DoClickUp(Click* click, int x, int y, unsigned modi) {
423     SkASSERT(click);
424 
425     SkView* target = (SkView*)SkEventSink::FindSink(click->fTargetID);
426     if (nullptr == target) {
427         return;
428     }
429 
430     click->fIPrev = click->fICurr;
431     click->fICurr.set(x, y);
432 
433     click->fPrev = click->fCurr;
434     click->fCurr.iset(x, y);
435     if (!target->globalToLocal(&click->fCurr)) {
436         // on failure pretend the mouse didn't move
437         click->fCurr = click->fPrev;
438     }
439 
440     click->fState = Click::kUp_State;
441     click->fModifierKeys = modi;
442     target->onClick(click);
443 }
444 
445 //////////////////////////////////////////////////////////////////////
446 
invokeLayout()447 void SkView::invokeLayout() {
448     SkView::Layout* layout = this->getLayout();
449 
450     if (layout) {
451         layout->layoutChildren(this);
452     }
453 }
454 
onDraw(SkCanvas * canvas)455 void SkView::onDraw(SkCanvas* canvas) {
456     Artist* artist = this->getArtist();
457 
458     if (artist) {
459         artist->draw(this, canvas);
460     }
461 }
462 
onSizeChange()463 void SkView::onSizeChange() {}
464 
onSendClickToChildren(SkScalar x,SkScalar y,unsigned modi)465 bool SkView::onSendClickToChildren(SkScalar x, SkScalar y, unsigned modi) {
466     return true;
467 }
468 
onFindClickHandler(SkScalar x,SkScalar y,unsigned modi)469 SkView::Click* SkView::onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) {
470     return nullptr;
471 }
472 
onClick(Click *)473 bool SkView::onClick(Click*) {
474     return false;
475 }
476 
handleInval(const SkRect *)477 bool SkView::handleInval(const SkRect*) {
478     return false;
479 }
480 
481 //////////////////////////////////////////////////////////////////////
482 
getLocalBounds(SkRect * bounds) const483 void SkView::getLocalBounds(SkRect* bounds) const {
484     if (bounds) {
485         bounds->set(0, 0, fWidth, fHeight);
486     }
487 }
488 
489 //////////////////////////////////////////////////////////////////////
490 //////////////////////////////////////////////////////////////////////
491 
detachFromParent_NoLayout()492 void SkView::detachFromParent_NoLayout() {
493     this->validate();
494     if (fParent == nullptr) {
495         return;
496     }
497 
498     if (fContainsFocus) {
499         (void)this->setFocusView(nullptr);
500     }
501 
502     this->inval(nullptr);
503 
504     SkView* next = nullptr;
505 
506     if (fNextSibling != this) {   // do we have any siblings
507         fNextSibling->fPrevSibling = fPrevSibling;
508         fPrevSibling->fNextSibling = fNextSibling;
509         next = fNextSibling;
510     }
511 
512     if (fParent->fFirstChild == this) {
513         fParent->fFirstChild = next;
514     }
515 
516     fParent = fNextSibling = fPrevSibling = nullptr;
517 
518     this->validate();
519     this->unref();
520 }
521 
detachFromParent()522 void SkView::detachFromParent() {
523     this->validate();
524     SkView* parent = fParent;
525 
526     if (parent) {
527         this->detachFromParent_NoLayout();
528         parent->invokeLayout();
529     }
530 }
531 
attachChildToBack(SkView * child)532 SkView* SkView::attachChildToBack(SkView* child) {
533     this->validate();
534     SkASSERT(child != this);
535 
536     if (child == nullptr || fFirstChild == child)
537         goto DONE;
538 
539     child->ref();
540     child->detachFromParent_NoLayout();
541 
542     if (fFirstChild == nullptr) {
543         child->fNextSibling = child;
544         child->fPrevSibling = child;
545     } else {
546         child->fNextSibling = fFirstChild;
547         child->fPrevSibling = fFirstChild->fPrevSibling;
548         fFirstChild->fPrevSibling->fNextSibling = child;
549         fFirstChild->fPrevSibling = child;
550     }
551 
552     fFirstChild = child;
553     child->fParent = this;
554     child->inval(nullptr);
555 
556     this->validate();
557     this->invokeLayout();
558 DONE:
559     return child;
560 }
561 
attachChildToFront(SkView * child)562 SkView* SkView::attachChildToFront(SkView* child) {
563     this->validate();
564     SkASSERT(child != this);
565 
566     if (child == nullptr || (fFirstChild && fFirstChild->fPrevSibling == child))
567         goto DONE;
568 
569     child->ref();
570     child->detachFromParent_NoLayout();
571 
572     if (fFirstChild == nullptr) {
573         fFirstChild = child;
574         child->fNextSibling = child;
575         child->fPrevSibling = child;
576     } else {
577         child->fNextSibling = fFirstChild;
578         child->fPrevSibling = fFirstChild->fPrevSibling;
579         fFirstChild->fPrevSibling->fNextSibling = child;
580         fFirstChild->fPrevSibling = child;
581     }
582 
583     child->fParent = this;
584     child->inval(nullptr);
585 
586     this->validate();
587     this->invokeLayout();
588 DONE:
589     return child;
590 }
591 
detachAllChildren()592 void SkView::detachAllChildren() {
593     this->validate();
594     while (fFirstChild)
595         fFirstChild->detachFromParent_NoLayout();
596 }
597 
localToGlobal(SkMatrix * matrix) const598 void SkView::localToGlobal(SkMatrix* matrix) const {
599     if (matrix) {
600         matrix->reset();
601         const SkView* view = this;
602         while (view)
603         {
604             matrix->preConcat(view->getLocalMatrix());
605             matrix->preTranslate(-view->fLoc.fX, -view->fLoc.fY);
606             view = view->fParent;
607         }
608     }
609 }
610 
globalToLocal(SkScalar x,SkScalar y,SkPoint * local) const611 bool SkView::globalToLocal(SkScalar x, SkScalar y, SkPoint* local) const {
612     if (local) {
613         SkMatrix m;
614         this->localToGlobal(&m);
615         if (!m.invert(&m)) {
616             return false;
617         }
618         SkPoint p;
619         m.mapXY(x, y, &p);
620         local->set(p.fX, p.fY);
621     }
622 
623     return true;
624 }
625 
626 //////////////////////////////////////////////////////////////////
627 
628 /*    Even if the subclass overrides onInflate, they should always be
629     sure to call the inherited method, so that we get called.
630 */
onInflate(const SkDOM & dom,const SkDOM::Node * node)631 void SkView::onInflate(const SkDOM& dom, const SkDOM::Node* node) {
632     SkScalar x, y;
633 
634     x = this->locX();
635     y = this->locY();
636     (void)dom.findScalar(node, "x", &x);
637     (void)dom.findScalar(node, "y", &y);
638     this->setLoc(x, y);
639 
640     x = this->width();
641     y = this->height();
642     (void)dom.findScalar(node, "width", &x);
643     (void)dom.findScalar(node, "height", &y);
644     this->setSize(x, y);
645 
646     // inflate the flags
647 
648     static const char* gFlagNames[] = {
649         "visible", "enabled", "focusable", "flexH", "flexV"
650     };
651     SkASSERT(SK_ARRAY_COUNT(gFlagNames) == kFlagShiftCount);
652 
653     bool     b;
654     uint32_t flags = this->getFlags();
655     for (unsigned i = 0; i < SK_ARRAY_COUNT(gFlagNames); i++) {
656         if (dom.findBool(node, gFlagNames[i], &b)) {
657             flags = SkSetClearShift(flags, b, i);
658         }
659     }
660     this->setFlags(flags);
661 }
662 
inflate(const SkDOM & dom,const SkDOM::Node * node)663 void SkView::inflate(const SkDOM& dom, const SkDOM::Node* node) {
664     this->onInflate(dom, node);
665 }
666 
667 //////////////////////////////////////////////////////////////////
668 
sendEventToParents(const SkEvent & evt)669 SkView* SkView::sendEventToParents(const SkEvent& evt) {
670     SkView* parent = fParent;
671 
672     while (parent) {
673         if (parent->doEvent(evt)) {
674             return parent;
675         }
676         parent = parent->fParent;
677     }
678     return nullptr;
679 }
680 
sendQueryToParents(SkEvent * evt)681 SkView* SkView::sendQueryToParents(SkEvent* evt) {
682     SkView* parent = fParent;
683 
684     while (parent) {
685         if (parent->doQuery(evt)) {
686             return parent;
687         }
688         parent = parent->fParent;
689     }
690     return nullptr;
691 }
692 
693 //////////////////////////////////////////////////////////////////
694 //////////////////////////////////////////////////////////////////
695 
F2BIter(const SkView * parent)696 SkView::F2BIter::F2BIter(const SkView* parent) {
697     fFirstChild = parent ? parent->fFirstChild : nullptr;
698     fChild = fFirstChild ? fFirstChild->fPrevSibling : nullptr;
699 }
700 
next()701 SkView* SkView::F2BIter::next() {
702     SkView* curr = fChild;
703 
704     if (fChild) {
705         if (fChild == fFirstChild) {
706             fChild = nullptr;
707         } else {
708             fChild = fChild->fPrevSibling;
709         }
710     }
711     return curr;
712 }
713 
B2FIter(const SkView * parent)714 SkView::B2FIter::B2FIter(const SkView* parent) {
715     fFirstChild = parent ? parent->fFirstChild : nullptr;
716     fChild = fFirstChild;
717 }
718 
next()719 SkView* SkView::B2FIter::next() {
720     SkView* curr = fChild;
721 
722     if (fChild) {
723         SkView* next = fChild->fNextSibling;
724         if (next == fFirstChild)
725             next = nullptr;
726         fChild = next;
727     }
728     return curr;
729 }
730 
731 //////////////////////////////////////////////////////////////////
732 //////////////////////////////////////////////////////////////////
733 
734 #ifdef SK_DEBUG
735 
validate() const736 void SkView::validate() const {
737 //    SkASSERT(this->getRefCnt() > 0 && this->getRefCnt() < 100);
738     if (fParent) {
739         SkASSERT(fNextSibling);
740         SkASSERT(fPrevSibling);
741     } else {
742         bool nextNull = nullptr == fNextSibling;
743         bool prevNull = nullptr == fNextSibling;
744         SkASSERT(nextNull == prevNull);
745     }
746 }
747 
show_if_nonzero(const char name[],SkScalar value)748 static inline void show_if_nonzero(const char name[], SkScalar value) {
749     if (value) {
750         SkDebugf("%s=\"%g\"", name, value/65536.);
751     }
752 }
753 
tab(int level)754 static void tab(int level) {
755     for (int i = 0; i < level; i++) {
756         SkDebugf("    ");
757     }
758 }
759 
dumpview(const SkView * view,int level,bool recurse)760 static void dumpview(const SkView* view, int level, bool recurse) {
761     tab(level);
762 
763     SkDebugf("<view");
764     show_if_nonzero(" x", view->locX());
765     show_if_nonzero(" y", view->locY());
766     show_if_nonzero(" width", view->width());
767     show_if_nonzero(" height", view->height());
768 
769     if (recurse) {
770         SkView::B2FIter    iter(view);
771         SkView*            child;
772         bool            noChildren = true;
773 
774         while ((child = iter.next()) != nullptr) {
775             if (noChildren) {
776                 SkDebugf(">\n");
777             }
778             noChildren = false;
779             dumpview(child, level + 1, true);
780         }
781 
782         if (!noChildren) {
783             tab(level);
784             SkDebugf("</view>\n");
785         } else {
786             goto ONELINER;
787         }
788     } else {
789     ONELINER:
790         SkDebugf(" />\n");
791     }
792 }
793 
dump(bool recurse) const794 void SkView::dump(bool recurse) const {
795     dumpview(this, 0, recurse);
796 }
797 
798 #endif
799