1// Copyright 2014 PDFium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7#include "xfa/src/foxitlib.h"
8#include "xfa/src/fwl/src/core/include/fwl_targetimp.h"
9#include "xfa/src/fwl/src/core/include/fwl_noteimp.h"
10#include "xfa/src/fwl/src/core/include/fwl_widgetmgrimp.h"
11#include "xfa/src/fwl/src/core/include/fwl_threadimp.h"
12#include "xfa/src/fwl/src/core/include/fwl_appimp.h"
13
14FX_BOOL FWL_UseOffscreen(IFWL_Widget* pWidget) {
15#if (_FX_OS_ == _FX_MACOSX_)
16  return FALSE;
17#else
18  return pWidget->GetStyles() & FWL_WGTSTYLE_Offscreen;
19#endif
20}
21IFWL_WidgetMgr* FWL_GetWidgetMgr() {
22  IFWL_App* pApp = FWL_GetApp();
23  if (!pApp)
24    return NULL;
25  return pApp->GetWidgetMgr();
26}
27CFWL_WidgetMgr::CFWL_WidgetMgr(IFWL_AdapterNative* pAdapterNative)
28    : m_dwCapability(0) {
29  m_pDelegate = new CFWL_WidgetMgrDelegate(this);
30  m_pAdapter = pAdapterNative->GetWidgetMgr(m_pDelegate);
31  FXSYS_assert(m_pAdapter);
32  CFWL_WidgetMgrItem* pRoot = new CFWL_WidgetMgrItem;
33  m_mapWidgetItem.SetAt(NULL, pRoot);
34#if (_FX_OS_ == _FX_WIN32_DESKTOP_) || (_FX_OS_ == _FX_WIN64_)
35  m_rtScreen.Reset();
36  IFWL_AdapterMonitorMgr* pMonitorMgr = pAdapterNative->GetMonitorMgr();
37  if (pMonitorMgr) {
38    FWL_HMONITOR monitor = pMonitorMgr->GetCurrentMonitor();
39    if (monitor) {
40      pMonitorMgr->GetMonitorSize(monitor, m_rtScreen.width, m_rtScreen.height);
41    }
42  }
43#endif
44}
45CFWL_WidgetMgr::~CFWL_WidgetMgr() {
46  FX_POSITION ps = m_mapWidgetItem.GetStartPosition();
47  while (ps) {
48    void* pWidget;
49    CFWL_WidgetMgrItem* pItem;
50    m_mapWidgetItem.GetNextAssoc(ps, pWidget, (void*&)pItem);
51    delete pItem;
52  }
53  m_mapWidgetItem.RemoveAll();
54  if (m_pDelegate) {
55    delete m_pDelegate;
56    m_pDelegate = NULL;
57  }
58}
59int32_t CFWL_WidgetMgr::CountWidgets(IFWL_Widget* pParent) {
60  CFWL_WidgetMgrItem* pParentItem = GetWidgetMgrItem(pParent);
61  return TravelWidgetMgr(pParentItem, NULL, NULL);
62}
63IFWL_Widget* CFWL_WidgetMgr::GetWidget(int32_t nIndex, IFWL_Widget* pParent) {
64  CFWL_WidgetMgrItem* pParentItem = GetWidgetMgrItem(pParent);
65  IFWL_Widget* pWidget = NULL;
66  TravelWidgetMgr(pParentItem, &nIndex, NULL, &pWidget);
67  return pWidget;
68}
69IFWL_Widget* CFWL_WidgetMgr::GetWidget(IFWL_Widget* pWidget,
70                                       FWL_WGTRELATION eRelation) {
71  CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pWidget);
72  if (!pItem) {
73    return NULL;
74  }
75  IFWL_Widget* pRet = NULL;
76  switch (eRelation) {
77    case FWL_WGTRELATION_Parent: {
78      pRet = pItem->pParent ? pItem->pParent->pWidget : NULL;
79      break;
80    }
81    case FWL_WGTRELATION_Owner: {
82      pRet = pItem->pOwner ? pItem->pOwner->pWidget : NULL;
83      break;
84    }
85    case FWL_WGTRELATION_FirstSibling: {
86      pItem = pItem->pPrevious;
87      while (pItem && pItem->pPrevious) {
88        pItem = pItem->pPrevious;
89      }
90      pRet = pItem ? pItem->pWidget : NULL;
91      break;
92    }
93    case FWL_WGTRELATION_PriorSibling: {
94      pRet = pItem->pPrevious ? pItem->pPrevious->pWidget : NULL;
95      break;
96    }
97    case FWL_WGTRELATION_NextSibling: {
98      pRet = pItem->pNext ? pItem->pNext->pWidget : NULL;
99      break;
100    }
101    case FWL_WGTRELATION_LastSibling: {
102      pItem = pItem->pNext;
103      while (pItem && pItem->pNext) {
104        pItem = pItem->pNext;
105      }
106      pRet = pItem ? pItem->pWidget : NULL;
107      break;
108    }
109    case FWL_WGTRELATION_FirstChild: {
110      pRet = pItem->pChild ? pItem->pChild->pWidget : NULL;
111      break;
112    }
113    case FWL_WGTRELATION_LastChild: {
114      pItem = pItem->pChild;
115      while (pItem && pItem->pNext) {
116        pItem = pItem->pNext;
117      }
118      pRet = pItem ? pItem->pWidget : NULL;
119      break;
120    }
121    case FWL_WGTRELATION_SystemForm: {
122      while (pItem) {
123        if (IsAbleNative(pItem->pWidget)) {
124          pRet = pItem->pWidget;
125          break;
126        }
127        pItem = pItem->pParent;
128      }
129      break;
130    }
131    default: {}
132  }
133  return pRet;
134}
135int32_t CFWL_WidgetMgr::GetWidgetIndex(IFWL_Widget* pWidget) {
136  CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pWidget);
137  if (!pItem)
138    return -1;
139  return TravelWidgetMgr(pItem->pParent, NULL, pItem);
140}
141FX_BOOL CFWL_WidgetMgr::SetWidgetIndex(IFWL_Widget* pWidget, int32_t nIndex) {
142  CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pWidget);
143  if (!pItem)
144    return FALSE;
145  if (!pItem->pParent)
146    return FALSE;
147  CFWL_WidgetMgrItem* pChild = pItem->pParent->pChild;
148  int32_t i = 0;
149  while (pChild) {
150    if (pChild == pItem) {
151      if (i == nIndex) {
152        return TRUE;
153      }
154      if (pChild->pPrevious) {
155        pChild->pPrevious->pNext = pChild->pNext;
156      }
157      if (pChild->pNext) {
158        pChild->pNext->pPrevious = pChild->pPrevious;
159      }
160      if (pItem->pParent->pChild == pItem) {
161        pItem->pParent->pChild = pItem->pNext;
162      }
163      pItem->pNext = NULL;
164      pItem->pPrevious = NULL;
165      break;
166    }
167    if (!pChild->pNext) {
168      break;
169    }
170    pChild = pChild->pNext;
171    ++i;
172  }
173  pChild = pItem->pParent->pChild;
174  if (pChild) {
175    if (nIndex < 0) {
176      while (pChild->pNext) {
177        pChild = pChild->pNext;
178      }
179      pChild->pNext = pItem;
180      pItem->pPrevious = pChild;
181      pItem->pNext = NULL;
182      return TRUE;
183    }
184    i = 0;
185    while (i < nIndex && pChild->pNext) {
186      pChild = pChild->pNext;
187      ++i;
188    }
189    if (!pChild->pNext) {
190      pChild->pNext = pItem;
191      pItem->pPrevious = pChild;
192      pItem->pNext = NULL;
193      return TRUE;
194    }
195    if (pChild->pPrevious) {
196      pItem->pPrevious = pChild->pPrevious;
197      pChild->pPrevious->pNext = pItem;
198    }
199    pChild->pPrevious = pItem;
200    pItem->pNext = pChild;
201    if (pItem->pParent->pChild == pChild) {
202      pItem->pParent->pChild = pItem;
203    }
204  } else {
205    pItem->pParent->pChild = pItem;
206    pItem->pPrevious = NULL;
207    pItem->pNext = NULL;
208  }
209  return TRUE;
210}
211FWL_ERR CFWL_WidgetMgr::RepaintWidget(IFWL_Widget* pWidget,
212                                      const CFX_RectF* pRect) {
213  if (!m_pAdapter)
214    return FWL_ERR_Indefinite;
215  IFWL_Widget* pNative = pWidget;
216  CFX_RectF rect(*pRect);
217  if (IsFormDisabled()) {
218    IFWL_Widget* pOuter = pWidget->GetOuter();
219    while (pOuter) {
220      CFX_RectF rtTemp;
221      pNative->GetWidgetRect(rtTemp);
222      rect.left += rtTemp.left;
223      rect.top += rtTemp.top;
224      pNative = pOuter;
225      pOuter = pOuter->GetOuter();
226    }
227  } else if (!IsAbleNative(pWidget)) {
228    pNative = GetWidget(pWidget, FWL_WGTRELATION_SystemForm);
229    if (!pNative)
230      return FWL_ERR_Indefinite;
231    pWidget->TransformTo(pNative, rect.left, rect.top);
232  }
233  AddRedrawCounts(pNative);
234  return m_pAdapter->RepaintWidget(pNative, &rect);
235}
236void CFWL_WidgetMgr::AddWidget(IFWL_Widget* pWidget) {
237  CFWL_WidgetMgrItem* pParentItem = GetWidgetMgrItem(NULL);
238  CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pWidget);
239  if (!pItem) {
240    pItem = new CFWL_WidgetMgrItem;
241    pItem->pWidget = pWidget;
242    m_mapWidgetItem.SetAt(pWidget, pItem);
243  }
244  if (pItem->pParent && pItem->pParent != pParentItem) {
245    if (pItem->pPrevious) {
246      pItem->pPrevious->pNext = pItem->pNext;
247    }
248    if (pItem->pNext) {
249      pItem->pNext->pPrevious = pItem->pPrevious;
250    }
251    if (pItem->pParent->pChild == pItem) {
252      pItem->pParent->pChild = pItem->pNext;
253    }
254  }
255  pItem->pParent = pParentItem;
256  SetWidgetIndex(pWidget, -1);
257}
258void CFWL_WidgetMgr::InsertWidget(IFWL_Widget* pParent,
259                                  IFWL_Widget* pChild,
260                                  int32_t nIndex) {
261  CFWL_WidgetMgrItem* pParentItem = GetWidgetMgrItem(pParent);
262  if (!pParentItem) {
263    pParentItem = new CFWL_WidgetMgrItem;
264    pParentItem->pWidget = pParent;
265    m_mapWidgetItem.SetAt(pParent, pParentItem);
266    CFWL_WidgetMgrItem* pRoot = GetWidgetMgrItem(NULL);
267    pParentItem->pParent = pRoot;
268    SetWidgetIndex(pParent, -1);
269  }
270  CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pChild);
271  if (!pItem) {
272    pItem = new CFWL_WidgetMgrItem;
273    pItem->pWidget = pChild;
274    m_mapWidgetItem.SetAt(pChild, pItem);
275  }
276  if (pItem->pParent && pItem->pParent != pParentItem) {
277    if (pItem->pPrevious) {
278      pItem->pPrevious->pNext = pItem->pNext;
279    }
280    if (pItem->pNext) {
281      pItem->pNext->pPrevious = pItem->pPrevious;
282    }
283    if (pItem->pParent->pChild == pItem) {
284      pItem->pParent->pChild = pItem->pNext;
285    }
286  }
287  pItem->pParent = pParentItem;
288  SetWidgetIndex(pChild, nIndex);
289}
290void CFWL_WidgetMgr::RemoveWidget(IFWL_Widget* pWidget) {
291  CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pWidget);
292  if (!pItem) {
293    return;
294  }
295  if (pItem->pPrevious) {
296    pItem->pPrevious->pNext = pItem->pNext;
297  }
298  if (pItem->pNext) {
299    pItem->pNext->pPrevious = pItem->pPrevious;
300  }
301  if (pItem->pParent && pItem->pParent->pChild == pItem) {
302    pItem->pParent->pChild = pItem->pNext;
303  }
304  CFWL_WidgetMgrItem* pChild = pItem->pChild;
305  while (pChild) {
306    CFWL_WidgetMgrItem* pNext = pChild->pNext;
307    RemoveWidget(pChild->pWidget);
308    pChild = pNext;
309  }
310  m_mapWidgetItem.RemoveKey(pWidget);
311  delete pItem;
312}
313void CFWL_WidgetMgr::SetOwner(IFWL_Widget* pOwner, IFWL_Widget* pOwned) {
314  CFWL_WidgetMgrItem* pParentItem = GetWidgetMgrItem(pOwner);
315  if (!pParentItem) {
316    pParentItem = new CFWL_WidgetMgrItem;
317    pParentItem->pWidget = pOwner;
318    m_mapWidgetItem.SetAt(pOwner, pParentItem);
319    CFWL_WidgetMgrItem* pRoot = GetWidgetMgrItem(NULL);
320    pParentItem->pParent = pRoot;
321    SetWidgetIndex(pOwner, -1);
322  }
323  CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pOwned);
324  if (!pItem) {
325    pItem = new CFWL_WidgetMgrItem;
326    pItem->pWidget = pOwned;
327    m_mapWidgetItem.SetAt(pOwned, pItem);
328  }
329  pItem->pOwner = pParentItem;
330}
331void CFWL_WidgetMgr::SetParent(IFWL_Widget* pParent, IFWL_Widget* pChild) {
332  CFWL_WidgetMgrItem* pParentItem = GetWidgetMgrItem(pParent);
333  CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pChild);
334  if (!pItem)
335    return;
336  if (pItem->pParent && pItem->pParent != pParentItem) {
337    if (pItem->pPrevious) {
338      pItem->pPrevious->pNext = pItem->pNext;
339    }
340    if (pItem->pNext) {
341      pItem->pNext->pPrevious = pItem->pPrevious;
342    }
343    if (pItem->pParent->pChild == pItem) {
344      pItem->pParent->pChild = pItem->pNext;
345    }
346    pItem->pNext = NULL;
347    pItem->pPrevious = NULL;
348  }
349  pItem->pParent = pParentItem;
350  SetWidgetIndex(pChild, -1);
351  if (!m_pAdapter)
352    return;
353  m_pAdapter->SetParentWidget(pChild, pParent);
354}
355FX_BOOL CFWL_WidgetMgr::IsChild(IFWL_Widget* pChild, IFWL_Widget* pParent) {
356  IFWL_Widget* pTemp = pChild;
357  do {
358    if (pTemp == pParent) {
359      return TRUE;
360    }
361    pTemp = GetWidget(pTemp, FWL_WGTRELATION_Parent);
362  } while (pTemp);
363  return FALSE;
364}
365FWL_ERR CFWL_WidgetMgr::CreateWidget_Native(IFWL_Widget* pWidget) {
366  if (!IsAbleNative(pWidget)) {
367    return FWL_ERR_Succeeded;
368  }
369  return m_pAdapter->CreateWidget(pWidget, pWidget->GetOwner());
370}
371FWL_ERR CFWL_WidgetMgr::DestroyWidget_Native(IFWL_Widget* pWidget) {
372  if (!IsAbleNative(pWidget)) {
373    return FWL_ERR_Succeeded;
374  }
375  return m_pAdapter->DestroyWidget(pWidget);
376}
377FWL_ERR CFWL_WidgetMgr::GetWidgetRect_Native(IFWL_Widget* pWidget,
378                                             CFX_RectF& rect) {
379  if (!IsAbleNative(pWidget)) {
380    return FWL_ERR_Succeeded;
381  }
382  return m_pAdapter->GetWidgetRect(pWidget, rect);
383}
384FWL_ERR CFWL_WidgetMgr::SetWidgetRect_Native(IFWL_Widget* pWidget,
385                                             const CFX_RectF& rect) {
386  if (FWL_UseOffscreen(pWidget)) {
387    CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pWidget);
388    pItem->iRedrawCounter++;
389    if (pItem->pOffscreen) {
390      CFX_RenderDevice* pDevice = pItem->pOffscreen->GetRenderDevice();
391      if (pDevice && pDevice->GetBitmap()) {
392        CFX_DIBitmap* pBitmap = pDevice->GetBitmap();
393        if (pBitmap->GetWidth() - rect.width > 1 ||
394            pBitmap->GetHeight() - rect.height > 1) {
395          delete pItem->pOffscreen;
396          pItem->pOffscreen = NULL;
397        }
398      }
399    }
400#if (_FX_OS_ == _FX_WIN32_DESKTOP_) || (_FX_OS_ == _FX_WIN64_)
401    pItem->bOutsideChanged = !m_rtScreen.Contains(rect);
402#endif
403  }
404  return m_pAdapter->SetWidgetRect(pWidget, rect);
405}
406FWL_ERR CFWL_WidgetMgr::SetWidgetPosition_Native(IFWL_Widget* pWidget,
407                                                 FX_FLOAT fx,
408                                                 FX_FLOAT fy) {
409  return m_pAdapter->SetWidgetPosition(pWidget, fx, fy);
410}
411FWL_ERR CFWL_WidgetMgr::SetWidgetIcon_Native(IFWL_Widget* pWidget,
412                                             const CFX_DIBitmap* pIcon,
413                                             FX_BOOL bBig) {
414  return m_pAdapter->SetWidgetIcon(pWidget, pIcon, bBig);
415}
416FWL_ERR CFWL_WidgetMgr::SetWidgetCaption_Native(
417    IFWL_Widget* pWidget,
418    const CFX_WideStringC& wsCaption) {
419  return m_pAdapter->SetWidgetCaption(pWidget, wsCaption);
420}
421FWL_ERR CFWL_WidgetMgr::SetBorderRegion_Native(IFWL_Widget* pWidget,
422                                               CFX_Path* pPath) {
423  return m_pAdapter->SetBorderRegion(pWidget, pPath);
424}
425FWL_ERR CFWL_WidgetMgr::ShowWidget_Native(IFWL_Widget* pWidget) {
426  return m_pAdapter->ShowWidget(pWidget);
427}
428FWL_ERR CFWL_WidgetMgr::HideWidget_Native(IFWL_Widget* pWidget) {
429  return m_pAdapter->HideWidget(pWidget);
430}
431FWL_ERR CFWL_WidgetMgr::SetNormal_Native(IFWL_Widget* pWidget) {
432  return m_pAdapter->SetNormal(pWidget);
433}
434FWL_ERR CFWL_WidgetMgr::SetMaximize_Native(IFWL_Widget* pWidget) {
435  return m_pAdapter->SetMaximize(pWidget);
436}
437FWL_ERR CFWL_WidgetMgr::SetMinimize_Native(IFWL_Widget* pWidget) {
438  return m_pAdapter->SetMinimize(pWidget);
439}
440FX_BOOL CFWL_WidgetMgr::CheckMessage_Native() {
441  return m_pAdapter->CheckMessage();
442}
443FWL_ERR CFWL_WidgetMgr::DispatchMessage_Native() {
444  return m_pAdapter->DispatchMessage();
445}
446FX_BOOL CFWL_WidgetMgr::IsIdleMessage_Native() {
447  return m_pAdapter->IsIdleMessage();
448}
449FWL_ERR CFWL_WidgetMgr::Exit_Native(int32_t iExitCode) {
450  return m_pAdapter->Exit(iExitCode);
451}
452FWL_ERR CFWL_WidgetMgr::CreateWidgetWithNativeId_Native(IFWL_Widget* pWidget,
453                                                        void* vp) {
454  return m_pAdapter->CreateWidgetWithNativeId(pWidget, vp);
455}
456IFWL_Widget* CFWL_WidgetMgr::GetWidgetAtPoint(IFWL_Widget* parent,
457                                              FX_FLOAT x,
458                                              FX_FLOAT y) {
459  if (!parent)
460    return NULL;
461  FX_FLOAT x1;
462  FX_FLOAT y1;
463  IFWL_Widget* child = GetWidget(parent, FWL_WGTRELATION_LastChild);
464  while (child) {
465    if ((child->GetStates() & FWL_WGTSTATE_Invisible) == 0) {
466      x1 = x;
467      y1 = y;
468      CFX_Matrix matrixOnParent;
469      child->GetMatrix(matrixOnParent);
470      CFX_Matrix m;
471      m.SetIdentity();
472      m.SetReverse(matrixOnParent);
473      m.TransformPoint(x1, y1);
474      CFX_RectF bounds;
475      child->GetWidgetRect(bounds);
476      if (bounds.Contains(x1, y1)) {
477        x1 -= bounds.left;
478        y1 -= bounds.top;
479        return GetWidgetAtPoint(child, x1, y1);
480      }
481    }
482    child = GetWidget(child, FWL_WGTRELATION_PriorSibling);
483  }
484  return parent;
485}
486void CFWL_WidgetMgr::NotifySizeChanged(IFWL_Widget* pForm,
487                                       FX_FLOAT fx,
488                                       FX_FLOAT fy) {
489  if (!FWL_UseOffscreen(pForm)) {
490    return;
491  }
492  CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pForm);
493  if (pItem->pOffscreen) {
494    delete pItem->pOffscreen;
495    pItem->pOffscreen = NULL;
496  }
497}
498IFWL_Widget* CFWL_WidgetMgr::nextTab(IFWL_Widget* parent,
499                                     IFWL_Widget* focus,
500                                     FX_BOOL& bFind) {
501  IFWL_Widget* child =
502      FWL_GetWidgetMgr()->GetWidget(parent, FWL_WGTRELATION_FirstChild);
503  while (child) {
504    if (focus == child) {
505      bFind = TRUE;
506    }
507    if ((child->GetStyles() & FWL_WGTSTYLE_TabStop) &&
508        (!focus || (focus != child && bFind))) {
509      return child;
510    }
511    IFWL_Widget* bRet = nextTab(child, focus, bFind);
512    if (bRet) {
513      return bRet;
514    }
515    child = FWL_GetWidgetMgr()->GetWidget(child, FWL_WGTRELATION_NextSibling);
516  }
517  return NULL;
518}
519int32_t CFWL_WidgetMgr::CountRadioButtonGroup(IFWL_Widget* pFirst) {
520  int32_t iRet = 0;
521  IFWL_Widget* pChild = pFirst;
522  while (pChild) {
523    if ((pChild->GetStyles() & FWL_WGTSTYLE_Group) &&
524        pChild->GetClassID() == 3811304691) {
525      iRet++;
526    }
527    pChild = GetWidget(pChild, FWL_WGTRELATION_NextSibling);
528  }
529  return iRet;
530}
531IFWL_Widget* CFWL_WidgetMgr::GetSiblingRadioButton(IFWL_Widget* pWidget,
532                                                   FX_BOOL bNext) {
533  while ((pWidget = GetWidget(pWidget, bNext ? FWL_WGTRELATION_NextSibling
534                                             : FWL_WGTRELATION_PriorSibling)) !=
535         NULL) {
536    if (pWidget->GetClassID() == 3811304691) {
537      return pWidget;
538    }
539  }
540  return NULL;
541}
542IFWL_Widget* CFWL_WidgetMgr::GetRadioButtonGroupHeader(
543    IFWL_Widget* pRadioButton) {
544  if (pRadioButton->GetStyles() & FWL_WGTSTYLE_Group) {
545    return pRadioButton;
546  }
547  IFWL_Widget* pNext = pRadioButton;
548  while ((pNext = GetSiblingRadioButton(pNext, FALSE)) != NULL) {
549    if (pNext->GetStyles() & FWL_WGTSTYLE_Group) {
550      return pNext;
551    }
552  }
553  pNext = GetWidget(pRadioButton, FWL_WGTRELATION_LastSibling);
554  if ((pNext->GetStyles() & FWL_WGTSTYLE_Group) &&
555      pNext->GetClassID() == 3811304691) {
556    return pNext;
557  }
558  while ((pNext = GetSiblingRadioButton(pNext, FALSE)) && pNext &&
559         pNext != pRadioButton) {
560    if (pNext->GetStyles() & FWL_WGTSTYLE_Group) {
561      return pNext;
562    }
563  }
564  pNext = GetWidget(pRadioButton, FWL_WGTRELATION_FirstSibling);
565  if (pNext && (pNext->GetStyles() == FWL_WGTSTYLE_Group) &&
566      pNext->GetClassID() == 3811304691) {
567    return pNext;
568  }
569  return GetSiblingRadioButton(pNext, TRUE);
570}
571void CFWL_WidgetMgr::GetSameGroupRadioButton(IFWL_Widget* pRadioButton,
572                                             CFX_PtrArray& group) {
573  IFWL_Widget* pFirst = GetWidget(pRadioButton, FWL_WGTRELATION_FirstSibling);
574  if (!pFirst) {
575    pFirst = pRadioButton;
576  }
577  int32_t iGroup = CountRadioButtonGroup(pFirst);
578  if (iGroup < 2) {
579    if (pFirst->GetClassID() == 3811304691) {
580      group.Add(pFirst);
581    }
582    IFWL_Widget* pNext = pFirst;
583    while ((pNext = GetSiblingRadioButton(pNext, TRUE)) != NULL) {
584      group.Add(pNext);
585    }
586    return;
587  }
588  IFWL_Widget* pNext = GetRadioButtonGroupHeader(pRadioButton);
589  do {
590    group.Add(pNext);
591    pNext = GetSiblingRadioButton(pNext, TRUE);
592    if (!pNext) {
593      if (pFirst->GetClassID() == 3811304691) {
594        pNext = pFirst;
595      } else {
596        pNext = GetSiblingRadioButton(pFirst, TRUE);
597      }
598    }
599  } while (pNext && ((pNext->GetStyles() & FWL_WGTSTYLE_Group) == 0));
600}
601IFWL_Widget* CFWL_WidgetMgr::GetDefaultButton(IFWL_Widget* pParent) {
602  if ((pParent->GetClassID() == 3521614244) &&
603      (pParent->GetStates() & (1 << (FWL_WGTSTATE_MAX + 2)))) {
604    return pParent;
605  }
606  IFWL_Widget* child =
607      FWL_GetWidgetMgr()->GetWidget(pParent, FWL_WGTRELATION_FirstChild);
608  while (child) {
609    if ((child->GetClassID() == 3521614244) &&
610        (child->GetStates() & (1 << (FWL_WGTSTATE_MAX + 2)))) {
611      return child;
612    }
613    IFWL_Widget* find = GetDefaultButton(child);
614    if (find) {
615      return find;
616    }
617    child = FWL_GetWidgetMgr()->GetWidget(child, FWL_WGTRELATION_NextSibling);
618  }
619  return NULL;
620}
621void CFWL_WidgetMgr::AddRedrawCounts(IFWL_Widget* pWidget) {
622  CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pWidget);
623  (pItem->iRedrawCounter)++;
624}
625void CFWL_WidgetMgr::ResetRedrawCounts(IFWL_Widget* pWidget) {
626  CFWL_WidgetMgrItem* pItem = GetWidgetMgrItem(pWidget);
627  pItem->iRedrawCounter = 0;
628}
629CFWL_WidgetMgrItem* CFWL_WidgetMgr::GetWidgetMgrItem(IFWL_Widget* pWidget) {
630  return static_cast<CFWL_WidgetMgrItem*>(m_mapWidgetItem.GetValueAt(pWidget));
631}
632int32_t CFWL_WidgetMgr::TravelWidgetMgr(CFWL_WidgetMgrItem* pParent,
633                                        int32_t* pIndex,
634                                        CFWL_WidgetMgrItem* pItem,
635                                        IFWL_Widget** pWidget) {
636  if (!pParent) {
637    return 0;
638  }
639  int32_t iCount = 0;
640  CFWL_WidgetMgrItem* pChild = pParent->pChild;
641  while (pChild) {
642    iCount++;
643    if (pIndex) {
644      if (*pIndex == 0) {
645        *pWidget = pChild->pWidget;
646        return iCount;
647      }
648      pIndex--;
649    }
650    if (pItem && pItem == pChild) {
651      return iCount - 1;
652    }
653    pChild = pChild->pNext;
654  }
655  if (pIndex) {
656    return 0;
657  } else if (pItem) {
658    return -1;
659  }
660  return iCount - 1;
661}
662FX_BOOL CFWL_WidgetMgr::IsAbleNative(IFWL_Widget* pWidget) {
663  if (!pWidget)
664    return FALSE;
665  if (!pWidget->IsInstance(FX_WSTRC(FWL_CLASS_Form))) {
666    return FALSE;
667  }
668  FX_DWORD dwStyles = pWidget->GetStyles();
669  return ((dwStyles & FWL_WGTSTYLE_WindowTypeMask) ==
670          FWL_WGTSTYLE_OverLapper) ||
671         (dwStyles & FWL_WGTSTYLE_Popup);
672}
673FX_BOOL CFWL_WidgetMgr::IsThreadEnabled() {
674  return !(m_dwCapability & FWL_WGTMGR_DisableThread);
675}
676FX_BOOL CFWL_WidgetMgr::IsFormDisabled() {
677  return m_dwCapability & FWL_WGTMGR_DisableForm;
678}
679FX_BOOL CFWL_WidgetMgr::GetAdapterPopupPos(IFWL_Widget* pWidget,
680                                           FX_FLOAT fMinHeight,
681                                           FX_FLOAT fMaxHeight,
682                                           const CFX_RectF& rtAnchor,
683                                           CFX_RectF& rtPopup) {
684  IFWL_AdapterWidgetMgr* pSDApapter = GetAdapterWidgetMgr();
685  return pSDApapter->GetPopupPos(pWidget, fMinHeight, fMaxHeight, rtAnchor,
686                                 rtPopup);
687}
688CFWL_WidgetMgrDelegate::CFWL_WidgetMgrDelegate(CFWL_WidgetMgr* pWidgetMgr)
689    : m_pWidgetMgr(pWidgetMgr) {}
690FWL_ERR CFWL_WidgetMgrDelegate::OnSetCapability(FX_DWORD dwCapability) {
691  m_pWidgetMgr->m_dwCapability = dwCapability;
692  return FWL_ERR_Succeeded;
693}
694int32_t CFWL_WidgetMgrDelegate::OnProcessMessageToForm(CFWL_Message* pMessage) {
695  if (!pMessage)
696    return 0;
697  if (!pMessage->m_pDstTarget)
698    return 0;
699  IFWL_Widget* pDstWidget = pMessage->m_pDstTarget;
700  IFWL_NoteThread* pNoteThread = pDstWidget->GetOwnerThread();
701  if (!pNoteThread)
702    return 0;
703  CFWL_NoteDriver* pNoteDriver =
704      static_cast<CFWL_NoteDriver*>(pNoteThread->GetNoteDriver());
705  if (!pNoteDriver)
706    return 0;
707  if (m_pWidgetMgr->IsThreadEnabled()) {
708    pMessage = static_cast<CFWL_Message*>(pMessage->Clone());
709  }
710  if (m_pWidgetMgr->IsFormDisabled()) {
711    pNoteDriver->ProcessMessage(pMessage);
712  } else {
713    pNoteDriver->QueueMessage(pMessage);
714  }
715#if (_FX_OS_ == _FX_MACOSX_)
716  CFWL_NoteLoop* pTopLoop = pNoteDriver->GetTopLoop();
717  if (pTopLoop) {
718    pNoteDriver->UnqueueMessage(pTopLoop);
719  }
720#endif
721  if (m_pWidgetMgr->IsThreadEnabled()) {
722    pMessage->Release();
723  }
724  return FWL_ERR_Succeeded;
725}
726FWL_ERR CFWL_WidgetMgrDelegate::OnDrawWidget(IFWL_Widget* pWidget,
727                                             CFX_Graphics* pGraphics,
728                                             const CFX_Matrix* pMatrix) {
729  if (!pWidget)
730    return FWL_ERR_Indefinite;
731  if (!pGraphics)
732    return FWL_ERR_Indefinite;
733  CFX_Graphics* pTemp = DrawWidgetBefore(pWidget, pGraphics, pMatrix);
734  CFX_RectF clipCopy;
735  pWidget->GetWidgetRect(clipCopy);
736  clipCopy.left = clipCopy.top = 0;
737  if (bUseOffscreenDirect(pWidget)) {
738    DrawWidgetAfter(pWidget, pGraphics, clipCopy, pMatrix);
739    return FWL_ERR_Succeeded;
740  }
741  CFX_RectF clipBounds;
742#if (_FX_OS_ == _FX_WIN32_DESKTOP_) || (_FX_OS_ == _FX_WIN64_) || \
743    (_FX_OS_ == _FX_LINUX_DESKTOP_) || (_FX_OS_ == _FX_ANDROID_)
744  IFWL_WidgetDelegate* pDelegate = pWidget->SetDelegate(NULL);
745  pDelegate->OnDrawWidget(pTemp, pMatrix);
746  pGraphics->GetClipRect(clipBounds);
747  clipCopy = clipBounds;
748#elif(_FX_OS_ == _FX_MACOSX_)
749  if (m_pWidgetMgr->IsFormDisabled()) {
750    IFWL_WidgetDelegate* pDelegate = pWidget->SetDelegate(NULL);
751    pDelegate->OnDrawWidget(pTemp, pMatrix);
752    pGraphics->GetClipRect(clipBounds);
753    clipCopy = clipBounds;
754  } else {
755    clipBounds.Set(pMatrix->a, pMatrix->b, pMatrix->c, pMatrix->d);
756    const_cast<CFX_Matrix*>(pMatrix)->SetIdentity();  // FIXME: const cast.
757#ifdef FWL_UseMacSystemBorder
758#else
759#endif
760    {
761      IFWL_WidgetDelegate* pDelegate = pWidget->SetDelegate(NULL);
762      pDelegate->OnDrawWidget(pTemp, pMatrix);
763    }
764  }
765#endif
766  if (!m_pWidgetMgr->IsFormDisabled()) {
767    CFX_RectF rtClient;
768    pWidget->GetClientRect(rtClient);
769    clipBounds.Intersect(rtClient);
770  }
771  if (!clipBounds.IsEmpty()) {
772    DrawChild(pWidget, clipBounds, pTemp, pMatrix);
773  }
774  DrawWidgetAfter(pWidget, pGraphics, clipCopy, pMatrix);
775  m_pWidgetMgr->ResetRedrawCounts(pWidget);
776  return FWL_ERR_Succeeded;
777}
778void CFWL_WidgetMgrDelegate::DrawChild(IFWL_Widget* parent,
779                                       const CFX_RectF& rtClip,
780                                       CFX_Graphics* pGraphics,
781                                       const CFX_Matrix* pMatrix) {
782  if (!parent)
783    return;
784  FX_BOOL bFormDisable = m_pWidgetMgr->IsFormDisabled();
785  IFWL_Widget* pNextChild =
786      m_pWidgetMgr->GetWidget(parent, FWL_WGTRELATION_FirstChild);
787  while (pNextChild) {
788    IFWL_Widget* child = pNextChild;
789    pNextChild = m_pWidgetMgr->GetWidget(child, FWL_WGTRELATION_NextSibling);
790    if (child->GetStates() & FWL_WGTSTATE_Invisible) {
791      continue;
792    }
793    CFX_RectF rtWidget;
794    child->GetWidgetRect(rtWidget);
795    if (rtWidget.IsEmpty()) {
796      continue;
797    }
798    CFX_Matrix widgetMatrix;
799    CFX_RectF clipBounds(rtWidget);
800    if (!bFormDisable) {
801      child->GetMatrix(widgetMatrix, TRUE);
802    }
803    if (pMatrix) {
804      widgetMatrix.Concat(*pMatrix);
805    }
806    if (!bFormDisable) {
807      widgetMatrix.TransformPoint(clipBounds.left, clipBounds.top);
808      clipBounds.Intersect(rtClip);
809      if (clipBounds.IsEmpty()) {
810        continue;
811      }
812      pGraphics->SaveGraphState();
813      pGraphics->SetClipRect(clipBounds);
814    }
815    widgetMatrix.Translate(rtWidget.left, rtWidget.top, TRUE);
816    IFWL_WidgetDelegate* pDelegate = child->SetDelegate(NULL);
817    if (pDelegate) {
818      if (m_pWidgetMgr->IsFormDisabled() ||
819          IsNeedRepaint(child, &widgetMatrix, rtClip)) {
820        pDelegate->OnDrawWidget(pGraphics, &widgetMatrix);
821      }
822    }
823    if (!bFormDisable) {
824      pGraphics->RestoreGraphState();
825    }
826    DrawChild(child, clipBounds, pGraphics,
827              bFormDisable ? &widgetMatrix : pMatrix);
828    child = m_pWidgetMgr->GetWidget(child, FWL_WGTRELATION_NextSibling);
829  }
830}
831CFX_Graphics* CFWL_WidgetMgrDelegate::DrawWidgetBefore(
832    IFWL_Widget* pWidget,
833    CFX_Graphics* pGraphics,
834    const CFX_Matrix* pMatrix) {
835  if (!FWL_UseOffscreen(pWidget)) {
836    return pGraphics;
837  }
838  CFWL_WidgetMgrItem* pItem = m_pWidgetMgr->GetWidgetMgrItem(pWidget);
839  if (!pItem->pOffscreen) {
840    pItem->pOffscreen = new CFX_Graphics;
841    CFX_RectF rect;
842    pWidget->GetWidgetRect(rect);
843    pItem->pOffscreen->Create((int32_t)rect.width, (int32_t)rect.height,
844                              FXDIB_Argb);
845  }
846  CFX_RectF rect;
847  pGraphics->GetClipRect(rect);
848  pItem->pOffscreen->SetClipRect(rect);
849  return pItem->pOffscreen;
850}
851void CFWL_WidgetMgrDelegate::DrawWidgetAfter(IFWL_Widget* pWidget,
852                                             CFX_Graphics* pGraphics,
853                                             CFX_RectF& rtClip,
854                                             const CFX_Matrix* pMatrix) {
855  if (FWL_UseOffscreen(pWidget)) {
856    CFWL_WidgetMgrItem* pItem = m_pWidgetMgr->GetWidgetMgrItem(pWidget);
857    pGraphics->Transfer(pItem->pOffscreen, rtClip.left, rtClip.top, rtClip,
858                        pMatrix);
859#ifdef _WIN32
860    pItem->pOffscreen->ClearClip();
861#endif
862  }
863  CFWL_WidgetMgrItem* pItem = m_pWidgetMgr->GetWidgetMgrItem(pWidget);
864  pItem->iRedrawCounter = 0;
865}
866#define FWL_NEEDREPAINTHIT_Point 12
867#define FWL_NEEDREPAINTHIT_Piece 3
868typedef struct _FWL_NeedRepaintHitData {
869  CFX_PointF hitPoint;
870  FX_BOOL bNotNeedRepaint;
871  FX_BOOL bNotContainByDirty;
872} FWL_NeedRepaintHitData;
873FX_BOOL CFWL_WidgetMgrDelegate::IsNeedRepaint(IFWL_Widget* pWidget,
874                                              CFX_Matrix* pMatrix,
875                                              const CFX_RectF& rtDirty) {
876  CFWL_WidgetMgrItem* pItem = m_pWidgetMgr->GetWidgetMgrItem(pWidget);
877  if (pItem && pItem->iRedrawCounter > 0) {
878    pItem->iRedrawCounter = 0;
879    return TRUE;
880  }
881  CFX_RectF rtWidget;
882  pWidget->GetWidgetRect(rtWidget);
883  rtWidget.left = rtWidget.top = 0;
884  pMatrix->TransformRect(rtWidget);
885  if (!rtWidget.IntersectWith(rtDirty)) {
886    return FALSE;
887  }
888  IFWL_Widget* pChild =
889      FWL_GetWidgetMgr()->GetWidget(pWidget, FWL_WGTRELATION_FirstChild);
890  if (!pChild) {
891    return TRUE;
892  }
893  if (pChild->GetClassID() == 3150298670) {
894    CFX_RectF rtTemp;
895    pChild->GetWidgetRect(rtTemp);
896    if (rtTemp.width >= rtWidget.width && rtTemp.height >= rtWidget.height) {
897      pChild =
898          FWL_GetWidgetMgr()->GetWidget(pChild, FWL_WGTRELATION_FirstChild);
899      if (!pChild) {
900        return TRUE;
901      }
902    }
903  }
904  CFX_RectF rtChilds;
905  rtChilds.Empty();
906  FX_BOOL bChildIntersectWithDirty = FALSE;
907  FX_BOOL bOrginPtIntersectWidthChild = FALSE;
908  FX_BOOL bOrginPtIntersectWidthDirty =
909      rtDirty.Contains(rtWidget.left, rtWidget.top);
910  static FWL_NeedRepaintHitData hitPoint[FWL_NEEDREPAINTHIT_Point];
911  static int32_t iSize = sizeof(FWL_NeedRepaintHitData);
912  FXSYS_memset(hitPoint, 0, iSize);
913  FX_FLOAT fxPiece = rtWidget.width / FWL_NEEDREPAINTHIT_Piece;
914  FX_FLOAT fyPiece = rtWidget.height / FWL_NEEDREPAINTHIT_Piece;
915  hitPoint[2].hitPoint.x = hitPoint[6].hitPoint.x = rtWidget.left;
916  hitPoint[0].hitPoint.x = hitPoint[3].hitPoint.x = hitPoint[7].hitPoint.x =
917      hitPoint[10].hitPoint.x = fxPiece + rtWidget.left;
918  hitPoint[1].hitPoint.x = hitPoint[4].hitPoint.x = hitPoint[8].hitPoint.x =
919      hitPoint[11].hitPoint.x = fxPiece * 2 + rtWidget.left;
920  hitPoint[5].hitPoint.x = hitPoint[9].hitPoint.x =
921      rtWidget.width + rtWidget.left;
922  hitPoint[0].hitPoint.y = hitPoint[1].hitPoint.y = rtWidget.top;
923  hitPoint[2].hitPoint.y = hitPoint[3].hitPoint.y = hitPoint[4].hitPoint.y =
924      hitPoint[5].hitPoint.y = fyPiece + rtWidget.top;
925  hitPoint[6].hitPoint.y = hitPoint[7].hitPoint.y = hitPoint[8].hitPoint.y =
926      hitPoint[9].hitPoint.y = fyPiece * 2 + rtWidget.top;
927  hitPoint[10].hitPoint.y = hitPoint[11].hitPoint.y =
928      rtWidget.height + rtWidget.top;
929  do {
930    CFX_RectF rect;
931    pChild->GetWidgetRect(rect);
932    CFX_RectF r = rect;
933    r.left += rtWidget.left;
934    r.top += rtWidget.top;
935    if (r.IsEmpty()) {
936      continue;
937    }
938    if (r.Contains(rtDirty)) {
939      return FALSE;
940    }
941    if (!bChildIntersectWithDirty && r.IntersectWith(rtDirty)) {
942      bChildIntersectWithDirty = TRUE;
943    }
944    if (bOrginPtIntersectWidthDirty && !bOrginPtIntersectWidthChild) {
945      bOrginPtIntersectWidthChild = rect.Contains(0, 0);
946    }
947    if (rtChilds.IsEmpty()) {
948      rtChilds = rect;
949    } else if (!(pChild->GetStates() & FWL_WGTSTATE_Invisible)) {
950      rtChilds.Union(rect);
951    }
952    for (int32_t i = 0; i < FWL_NEEDREPAINTHIT_Point; i++) {
953      if (hitPoint[i].bNotContainByDirty || hitPoint[i].bNotNeedRepaint) {
954        continue;
955      }
956      if (!rtDirty.Contains(hitPoint[i].hitPoint)) {
957        hitPoint[i].bNotContainByDirty = TRUE;
958        continue;
959      }
960      if (r.Contains(hitPoint[i].hitPoint)) {
961        hitPoint[i].bNotNeedRepaint = TRUE;
962      }
963    }
964  } while ((pChild = FWL_GetWidgetMgr()->GetWidget(
965                pChild, FWL_WGTRELATION_NextSibling)) != NULL);
966  if (!bChildIntersectWithDirty) {
967    return TRUE;
968  }
969  if (bOrginPtIntersectWidthDirty && !bOrginPtIntersectWidthChild) {
970    return TRUE;
971  }
972  if (rtChilds.IsEmpty()) {
973    return TRUE;
974  }
975  int32_t repaintPoint = FWL_NEEDREPAINTHIT_Point;
976  for (int32_t i = 0; i < FWL_NEEDREPAINTHIT_Point; i++) {
977    if (hitPoint[i].bNotNeedRepaint) {
978      repaintPoint--;
979    }
980  }
981  if (repaintPoint > 0) {
982    return TRUE;
983  }
984  pMatrix->TransformRect(rtChilds);
985  if (rtChilds.Contains(rtDirty) || rtChilds.Contains(rtWidget)) {
986    return FALSE;
987  }
988  return TRUE;
989}
990FX_BOOL CFWL_WidgetMgrDelegate::bUseOffscreenDirect(IFWL_Widget* pWidget) {
991  CFWL_WidgetMgrItem* pItem = m_pWidgetMgr->GetWidgetMgrItem(pWidget);
992  if (!FWL_UseOffscreen(pWidget) || !(pItem->pOffscreen)) {
993    return FALSE;
994  }
995#if (_FX_OS_ == _FX_WIN32_DESKTOP_) || (_FX_OS_ == _FX_WIN64_)
996  if (pItem->bOutsideChanged) {
997    CFX_RectF r;
998    pWidget->GetWidgetRect(r);
999    CFX_RectF temp(m_pWidgetMgr->m_rtScreen);
1000    temp.Deflate(50, 50);
1001    if (!temp.Contains(r)) {
1002      return FALSE;
1003    }
1004    pItem->bOutsideChanged = FALSE;
1005  }
1006#endif
1007  return pItem->iRedrawCounter == 0;
1008}
1009static void FWL_WriteBMP(CFX_DIBitmap* pBitmap, const FX_CHAR* filename) {
1010  FILE* file = fopen(filename, "wb");
1011  if (file == NULL) {
1012    return;
1013  }
1014  int size = 14 + 40 + pBitmap->GetPitch() * pBitmap->GetHeight();
1015  unsigned char buffer[40];
1016  buffer[0] = 'B';
1017  buffer[1] = 'M';
1018  buffer[2] = (unsigned char)size;
1019  buffer[3] = (unsigned char)(size >> 8);
1020  buffer[4] = (unsigned char)(size >> 16);
1021  buffer[5] = (unsigned char)(size >> 24);
1022  buffer[6] = buffer[7] = buffer[8] = buffer[9] = 0;
1023  buffer[10] = 54;
1024  buffer[11] = buffer[12] = buffer[13] = 0;
1025  fwrite(buffer, 14, 1, file);
1026  memset(buffer, 0, 40);
1027  buffer[0] = 40;
1028  buffer[4] = (unsigned char)pBitmap->GetWidth();
1029  buffer[5] = (unsigned char)(pBitmap->GetWidth() >> 8);
1030  buffer[6] = (unsigned char)(pBitmap->GetWidth() >> 16);
1031  buffer[7] = (unsigned char)(pBitmap->GetWidth() >> 24);
1032  buffer[8] = (unsigned char)(-pBitmap->GetHeight());
1033  buffer[9] = (unsigned char)((-pBitmap->GetHeight()) >> 8);
1034  buffer[10] = (unsigned char)((-pBitmap->GetHeight()) >> 16);
1035  buffer[11] = (unsigned char)((-pBitmap->GetHeight()) >> 24);
1036  buffer[12] = 1;
1037  buffer[14] = pBitmap->GetBPP();
1038  fwrite(buffer, 40, 1, file);
1039  for (int row = 0; row < pBitmap->GetHeight(); row++) {
1040    uint8_t* scan_line = pBitmap->GetBuffer() + row * pBitmap->GetPitch();
1041    fwrite(scan_line, pBitmap->GetPitch(), 1, file);
1042  }
1043  fclose(file);
1044}
1045FWL_ERR FWL_WidgetMgrSnapshot(IFWL_Widget* pWidget,
1046                              const CFX_WideString* saveFile,
1047                              const CFX_Matrix* pMatrix) {
1048  CFX_RectF r;
1049  pWidget->GetWidgetRect(r);
1050  CFX_Graphics gs;
1051  gs.Create((int32_t)r.width, (int32_t)r.height, FXDIB_Argb);
1052  CFWL_WidgetMgr* widgetMgr = static_cast<CFWL_WidgetMgr*>(FWL_GetWidgetMgr());
1053  CFWL_WidgetMgrDelegate* delegate = widgetMgr->GetDelegate();
1054  delegate->OnDrawWidget(pWidget, &gs, pMatrix);
1055  CFX_DIBitmap* dib = gs.GetRenderDevice()->GetBitmap();
1056  FWL_WriteBMP(dib, saveFile->UTF8Encode());
1057  return FWL_ERR_Succeeded;
1058}
1059FX_BOOL FWL_WidgetIsChild(IFWL_Widget* parent, IFWL_Widget* find) {
1060  if (!find) {
1061    return FALSE;
1062  }
1063  IFWL_Widget* child =
1064      FWL_GetWidgetMgr()->GetWidget(parent, FWL_WGTRELATION_FirstChild);
1065  while (child) {
1066    if (child == find) {
1067      return TRUE;
1068    }
1069    if (FWL_WidgetIsChild(child, find)) {
1070      return TRUE;
1071    }
1072    child = FWL_GetWidgetMgr()->GetWidget(child, FWL_WGTRELATION_NextSibling);
1073  }
1074  return FALSE;
1075}
1076