• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/fwl/cfwl_combobox.h"
8 
9 #include <algorithm>
10 #include <memory>
11 #include <utility>
12 
13 #include "third_party/base/ptr_util.h"
14 #include "xfa/fde/cfde_txtedtengine.h"
15 #include "xfa/fde/tto/fde_textout.h"
16 #include "xfa/fwl/cfwl_app.h"
17 #include "xfa/fwl/cfwl_event.h"
18 #include "xfa/fwl/cfwl_eventselectchanged.h"
19 #include "xfa/fwl/cfwl_eventtextchanged.h"
20 #include "xfa/fwl/cfwl_formproxy.h"
21 #include "xfa/fwl/cfwl_listbox.h"
22 #include "xfa/fwl/cfwl_messagekey.h"
23 #include "xfa/fwl/cfwl_messagekillfocus.h"
24 #include "xfa/fwl/cfwl_messagemouse.h"
25 #include "xfa/fwl/cfwl_messagesetfocus.h"
26 #include "xfa/fwl/cfwl_notedriver.h"
27 #include "xfa/fwl/cfwl_themebackground.h"
28 #include "xfa/fwl/cfwl_themepart.h"
29 #include "xfa/fwl/cfwl_themetext.h"
30 #include "xfa/fwl/cfwl_widgetmgr.h"
31 #include "xfa/fwl/ifwl_themeprovider.h"
32 
CFWL_ComboBox(const CFWL_App * app)33 CFWL_ComboBox::CFWL_ComboBox(const CFWL_App* app)
34     : CFWL_Widget(app, pdfium::MakeUnique<CFWL_WidgetProperties>(), nullptr),
35       m_pComboBoxProxy(nullptr),
36       m_bLButtonDown(false),
37       m_iCurSel(-1),
38       m_iBtnState(CFWL_PartState_Normal) {
39   m_rtClient.Reset();
40   m_rtBtn.Reset();
41   m_rtHandler.Reset();
42 
43   if (m_pWidgetMgr->IsFormDisabled()) {
44     DisForm_InitComboList();
45     DisForm_InitComboEdit();
46     return;
47   }
48 
49   auto prop = pdfium::MakeUnique<CFWL_WidgetProperties>();
50   prop->m_pThemeProvider = m_pProperties->m_pThemeProvider;
51   prop->m_dwStyles |= FWL_WGTSTYLE_Border | FWL_WGTSTYLE_VScroll;
52   m_pListBox =
53       pdfium::MakeUnique<CFWL_ComboList>(m_pOwnerApp, std::move(prop), this);
54 
55   if ((m_pProperties->m_dwStyleExes & FWL_STYLEEXT_CMB_DropDown) && !m_pEdit) {
56     m_pEdit = pdfium::MakeUnique<CFWL_ComboEdit>(
57         m_pOwnerApp, pdfium::MakeUnique<CFWL_WidgetProperties>(), this);
58     m_pEdit->SetOuter(this);
59   }
60   if (m_pEdit)
61     m_pEdit->SetParent(this);
62 
63   SetStates(m_pProperties->m_dwStates);
64 }
65 
~CFWL_ComboBox()66 CFWL_ComboBox::~CFWL_ComboBox() {}
67 
GetClassID() const68 FWL_Type CFWL_ComboBox::GetClassID() const {
69   return FWL_Type::ComboBox;
70 }
71 
AddString(const CFX_WideStringC & wsText)72 void CFWL_ComboBox::AddString(const CFX_WideStringC& wsText) {
73   m_pListBox->AddString(wsText);
74 }
75 
RemoveAt(int32_t iIndex)76 void CFWL_ComboBox::RemoveAt(int32_t iIndex) {
77   m_pListBox->RemoveAt(iIndex);
78 }
79 
RemoveAll()80 void CFWL_ComboBox::RemoveAll() {
81   m_pListBox->DeleteAll();
82 }
83 
ModifyStylesEx(uint32_t dwStylesExAdded,uint32_t dwStylesExRemoved)84 void CFWL_ComboBox::ModifyStylesEx(uint32_t dwStylesExAdded,
85                                    uint32_t dwStylesExRemoved) {
86   if (m_pWidgetMgr->IsFormDisabled()) {
87     DisForm_ModifyStylesEx(dwStylesExAdded, dwStylesExRemoved);
88     return;
89   }
90 
91   bool bAddDropDown = !!(dwStylesExAdded & FWL_STYLEEXT_CMB_DropDown);
92   bool bRemoveDropDown = !!(dwStylesExRemoved & FWL_STYLEEXT_CMB_DropDown);
93   if (bAddDropDown && !m_pEdit) {
94     m_pEdit = pdfium::MakeUnique<CFWL_ComboEdit>(
95         m_pOwnerApp, pdfium::MakeUnique<CFWL_WidgetProperties>(), nullptr);
96     m_pEdit->SetOuter(this);
97     m_pEdit->SetParent(this);
98   } else if (bRemoveDropDown && m_pEdit) {
99     m_pEdit->SetStates(FWL_WGTSTATE_Invisible);
100   }
101   CFWL_Widget::ModifyStylesEx(dwStylesExAdded, dwStylesExRemoved);
102 }
103 
Update()104 void CFWL_ComboBox::Update() {
105   if (m_pWidgetMgr->IsFormDisabled()) {
106     DisForm_Update();
107     return;
108   }
109   if (IsLocked())
110     return;
111 
112   ResetTheme();
113   if (IsDropDownStyle() && m_pEdit)
114     ResetEditAlignment();
115   if (!m_pProperties->m_pThemeProvider)
116     m_pProperties->m_pThemeProvider = GetAvailableTheme();
117 
118   Layout();
119 }
120 
HitTest(const CFX_PointF & point)121 FWL_WidgetHit CFWL_ComboBox::HitTest(const CFX_PointF& point) {
122   if (m_pWidgetMgr->IsFormDisabled())
123     return DisForm_HitTest(point);
124   return CFWL_Widget::HitTest(point);
125 }
126 
DrawWidget(CFX_Graphics * pGraphics,const CFX_Matrix * pMatrix)127 void CFWL_ComboBox::DrawWidget(CFX_Graphics* pGraphics,
128                                const CFX_Matrix* pMatrix) {
129   if (m_pWidgetMgr->IsFormDisabled()) {
130     DisForm_DrawWidget(pGraphics, pMatrix);
131     return;
132   }
133 
134   if (!pGraphics)
135     return;
136   if (!m_pProperties->m_pThemeProvider)
137     return;
138 
139   IFWL_ThemeProvider* pTheme = m_pProperties->m_pThemeProvider;
140   if (HasBorder())
141     DrawBorder(pGraphics, CFWL_Part::Border, pTheme, pMatrix);
142 
143   if (!IsDropDownStyle()) {
144     CFX_RectF rtTextBk(m_rtClient);
145     rtTextBk.width -= m_rtBtn.width;
146 
147     CFWL_ThemeBackground param;
148     param.m_pWidget = this;
149     param.m_iPart = CFWL_Part::Background;
150     param.m_pGraphics = pGraphics;
151     if (pMatrix)
152       param.m_matrix.Concat(*pMatrix);
153     param.m_rtPart = rtTextBk;
154 
155     if (m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled) {
156       param.m_dwStates = CFWL_PartState_Disabled;
157     } else if ((m_pProperties->m_dwStates & FWL_WGTSTATE_Focused) &&
158                (m_iCurSel >= 0)) {
159       param.m_dwStates = CFWL_PartState_Selected;
160     } else {
161       param.m_dwStates = CFWL_PartState_Normal;
162     }
163     pTheme->DrawBackground(&param);
164 
165     if (m_iCurSel >= 0) {
166       if (!m_pListBox)
167         return;
168 
169       CFWL_ListItem* hItem = m_pListBox->GetItem(this, m_iCurSel);
170 
171       CFWL_ThemeText theme_text;
172       theme_text.m_pWidget = this;
173       theme_text.m_iPart = CFWL_Part::Caption;
174       theme_text.m_dwStates = m_iBtnState;
175       theme_text.m_pGraphics = pGraphics;
176       theme_text.m_matrix.Concat(*pMatrix);
177       theme_text.m_rtPart = rtTextBk;
178       theme_text.m_dwStates = (m_pProperties->m_dwStates & FWL_WGTSTATE_Focused)
179                                   ? CFWL_PartState_Selected
180                                   : CFWL_PartState_Normal;
181       theme_text.m_wsText = hItem ? hItem->GetText() : L"";
182       theme_text.m_dwTTOStyles = FDE_TTOSTYLE_SingleLine;
183       theme_text.m_iTTOAlign = FDE_TTOALIGNMENT_CenterLeft;
184       pTheme->DrawText(&theme_text);
185     }
186   }
187 
188   CFWL_ThemeBackground param;
189   param.m_pWidget = this;
190   param.m_iPart = CFWL_Part::DropDownButton;
191   param.m_dwStates = (m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled)
192                          ? CFWL_PartState_Disabled
193                          : m_iBtnState;
194   param.m_pGraphics = pGraphics;
195   param.m_matrix.Concat(*pMatrix);
196   param.m_rtPart = m_rtBtn;
197   pTheme->DrawBackground(&param);
198 }
199 
SetThemeProvider(IFWL_ThemeProvider * pThemeProvider)200 void CFWL_ComboBox::SetThemeProvider(IFWL_ThemeProvider* pThemeProvider) {
201   if (!pThemeProvider)
202     return;
203 
204   m_pProperties->m_pThemeProvider = pThemeProvider;
205   if (m_pListBox)
206     m_pListBox->SetThemeProvider(pThemeProvider);
207   if (m_pEdit)
208     m_pEdit->SetThemeProvider(pThemeProvider);
209 }
210 
GetTextByIndex(int32_t iIndex) const211 CFX_WideString CFWL_ComboBox::GetTextByIndex(int32_t iIndex) const {
212   CFWL_ListItem* pItem = static_cast<CFWL_ListItem*>(
213       m_pListBox->GetItem(m_pListBox.get(), iIndex));
214   return pItem ? pItem->GetText() : L"";
215 }
216 
SetCurSel(int32_t iSel)217 void CFWL_ComboBox::SetCurSel(int32_t iSel) {
218   int32_t iCount = m_pListBox->CountItems(nullptr);
219   bool bClearSel = iSel < 0 || iSel >= iCount;
220   if (IsDropDownStyle() && m_pEdit) {
221     if (bClearSel) {
222       m_pEdit->SetText(CFX_WideString());
223     } else {
224       CFWL_ListItem* hItem = m_pListBox->GetItem(this, iSel);
225       m_pEdit->SetText(hItem ? hItem->GetText() : L"");
226     }
227     m_pEdit->Update();
228   }
229   m_iCurSel = bClearSel ? -1 : iSel;
230 }
231 
SetStates(uint32_t dwStates)232 void CFWL_ComboBox::SetStates(uint32_t dwStates) {
233   if (IsDropDownStyle() && m_pEdit)
234     m_pEdit->SetStates(dwStates);
235   if (m_pListBox)
236     m_pListBox->SetStates(dwStates);
237   CFWL_Widget::SetStates(dwStates);
238 }
239 
RemoveStates(uint32_t dwStates)240 void CFWL_ComboBox::RemoveStates(uint32_t dwStates) {
241   if (IsDropDownStyle() && m_pEdit)
242     m_pEdit->RemoveStates(dwStates);
243   if (m_pListBox)
244     m_pListBox->RemoveStates(dwStates);
245   CFWL_Widget::RemoveStates(dwStates);
246 }
247 
SetEditText(const CFX_WideString & wsText)248 void CFWL_ComboBox::SetEditText(const CFX_WideString& wsText) {
249   if (!m_pEdit)
250     return;
251 
252   m_pEdit->SetText(wsText);
253   m_pEdit->Update();
254 }
255 
GetEditText() const256 CFX_WideString CFWL_ComboBox::GetEditText() const {
257   if (m_pEdit)
258     return m_pEdit->GetText();
259   if (!m_pListBox)
260     return L"";
261 
262   CFWL_ListItem* hItem = m_pListBox->GetItem(this, m_iCurSel);
263   return hItem ? hItem->GetText() : L"";
264 }
265 
OpenDropDownList(bool bActivate)266 void CFWL_ComboBox::OpenDropDownList(bool bActivate) {
267   ShowDropList(bActivate);
268 }
269 
GetBBox() const270 CFX_RectF CFWL_ComboBox::GetBBox() const {
271   if (m_pWidgetMgr->IsFormDisabled())
272     return DisForm_GetBBox();
273 
274   CFX_RectF rect = m_pProperties->m_rtWidget;
275   if (!m_pListBox || !IsDropListVisible())
276     return rect;
277 
278   CFX_RectF rtList = m_pListBox->GetWidgetRect();
279   rtList.Offset(rect.left, rect.top);
280   rect.Union(rtList);
281   return rect;
282 }
283 
EditModifyStylesEx(uint32_t dwStylesExAdded,uint32_t dwStylesExRemoved)284 void CFWL_ComboBox::EditModifyStylesEx(uint32_t dwStylesExAdded,
285                                        uint32_t dwStylesExRemoved) {
286   if (m_pEdit)
287     m_pEdit->ModifyStylesEx(dwStylesExAdded, dwStylesExRemoved);
288 }
289 
DrawStretchHandler(CFX_Graphics * pGraphics,const CFX_Matrix * pMatrix)290 void CFWL_ComboBox::DrawStretchHandler(CFX_Graphics* pGraphics,
291                                        const CFX_Matrix* pMatrix) {
292   CFWL_ThemeBackground param;
293   param.m_pGraphics = pGraphics;
294   param.m_iPart = CFWL_Part::StretchHandler;
295   param.m_dwStates = CFWL_PartState_Normal;
296   param.m_pWidget = this;
297   if (pMatrix)
298     param.m_matrix.Concat(*pMatrix);
299   param.m_rtPart = m_rtHandler;
300   m_pProperties->m_pThemeProvider->DrawBackground(&param);
301 }
302 
ShowDropList(bool bActivate)303 void CFWL_ComboBox::ShowDropList(bool bActivate) {
304   if (m_pWidgetMgr->IsFormDisabled())
305     return DisForm_ShowDropList(bActivate);
306   if (IsDropListVisible() == bActivate)
307     return;
308   if (!m_pComboBoxProxy)
309     InitProxyForm();
310 
311   m_pComboBoxProxy->Reset();
312   if (!bActivate) {
313     m_pComboBoxProxy->EndDoModal();
314 
315     m_bLButtonDown = false;
316     m_pListBox->SetNotifyOwner(true);
317     SetFocus(true);
318     return;
319   }
320 
321   m_pListBox->ChangeSelected(m_iCurSel);
322   ResetListItemAlignment();
323 
324   uint32_t dwStyleAdd = m_pProperties->m_dwStyleExes &
325                         (FWL_STYLEEXT_CMB_Sort | FWL_STYLEEXT_CMB_OwnerDraw);
326   m_pListBox->ModifyStylesEx(dwStyleAdd, 0);
327   m_rtList = m_pListBox->GetAutosizedWidgetRect();
328 
329   CFX_RectF rtAnchor(0, 0, m_pProperties->m_rtWidget.width,
330                      m_pProperties->m_rtWidget.height);
331 
332   m_rtList.width = std::max(m_rtList.width, m_rtClient.width);
333   m_rtProxy = m_rtList;
334 
335   GetPopupPos(0, m_rtProxy.height, rtAnchor, m_rtProxy);
336 
337   m_pComboBoxProxy->SetWidgetRect(m_rtProxy);
338   m_pComboBoxProxy->Update();
339   m_pListBox->SetWidgetRect(m_rtList);
340   m_pListBox->Update();
341 
342   CFWL_Event ev(CFWL_Event::Type::PreDropDown, this);
343   DispatchEvent(&ev);
344 
345   m_pListBox->SetFocus(true);
346   m_pComboBoxProxy->DoModal();
347   m_pListBox->SetFocus(false);
348 }
349 
MatchEditText()350 void CFWL_ComboBox::MatchEditText() {
351   CFX_WideString wsText = m_pEdit->GetText();
352   int32_t iMatch = m_pListBox->MatchItem(wsText);
353   if (iMatch != m_iCurSel) {
354     m_pListBox->ChangeSelected(iMatch);
355     if (iMatch >= 0)
356       SyncEditText(iMatch);
357   } else if (iMatch >= 0) {
358     m_pEdit->SetSelected();
359   }
360   m_iCurSel = iMatch;
361 }
362 
SyncEditText(int32_t iListItem)363 void CFWL_ComboBox::SyncEditText(int32_t iListItem) {
364   CFWL_ListItem* hItem = m_pListBox->GetItem(this, iListItem);
365   m_pEdit->SetText(hItem ? hItem->GetText() : L"");
366   m_pEdit->Update();
367   m_pEdit->SetSelected();
368 }
369 
Layout()370 void CFWL_ComboBox::Layout() {
371   if (m_pWidgetMgr->IsFormDisabled())
372     return DisForm_Layout();
373 
374   m_rtClient = GetClientRect();
375   IFWL_ThemeProvider* theme = GetAvailableTheme();
376   if (!theme)
377     return;
378 
379   FX_FLOAT fBtn = theme->GetScrollBarWidth();
380   m_rtBtn = CFX_RectF(m_rtClient.right() - fBtn, m_rtClient.top, fBtn,
381                       m_rtClient.height);
382   if (!IsDropDownStyle() || !m_pEdit)
383     return;
384 
385   CFX_RectF rtEdit(m_rtClient.left, m_rtClient.top, m_rtClient.width - fBtn,
386                    m_rtClient.height);
387   m_pEdit->SetWidgetRect(rtEdit);
388 
389   if (m_iCurSel >= 0) {
390     CFWL_ListItem* hItem = m_pListBox->GetItem(this, m_iCurSel);
391     m_pEdit->LockUpdate();
392     m_pEdit->SetText(hItem ? hItem->GetText() : L"");
393     m_pEdit->UnlockUpdate();
394   }
395   m_pEdit->Update();
396 }
397 
ResetTheme()398 void CFWL_ComboBox::ResetTheme() {
399   IFWL_ThemeProvider* pTheme = m_pProperties->m_pThemeProvider;
400   if (!pTheme) {
401     pTheme = GetAvailableTheme();
402     m_pProperties->m_pThemeProvider = pTheme;
403   }
404   if (m_pListBox && !m_pListBox->GetThemeProvider())
405     m_pListBox->SetThemeProvider(pTheme);
406   if (m_pEdit && !m_pEdit->GetThemeProvider())
407     m_pEdit->SetThemeProvider(pTheme);
408 }
409 
ResetEditAlignment()410 void CFWL_ComboBox::ResetEditAlignment() {
411   if (!m_pEdit)
412     return;
413 
414   uint32_t dwAdd = 0;
415   switch (m_pProperties->m_dwStyleExes & FWL_STYLEEXT_CMB_EditHAlignMask) {
416     case FWL_STYLEEXT_CMB_EditHCenter: {
417       dwAdd |= FWL_STYLEEXT_EDT_HCenter;
418       break;
419     }
420     default: {
421       dwAdd |= FWL_STYLEEXT_EDT_HNear;
422       break;
423     }
424   }
425   switch (m_pProperties->m_dwStyleExes & FWL_STYLEEXT_CMB_EditVAlignMask) {
426     case FWL_STYLEEXT_CMB_EditVCenter: {
427       dwAdd |= FWL_STYLEEXT_EDT_VCenter;
428       break;
429     }
430     case FWL_STYLEEXT_CMB_EditVFar: {
431       dwAdd |= FWL_STYLEEXT_EDT_VFar;
432       break;
433     }
434     default: {
435       dwAdd |= FWL_STYLEEXT_EDT_VNear;
436       break;
437     }
438   }
439   if (m_pProperties->m_dwStyleExes & FWL_STYLEEXT_CMB_EditJustified)
440     dwAdd |= FWL_STYLEEXT_EDT_Justified;
441 
442   m_pEdit->ModifyStylesEx(dwAdd, FWL_STYLEEXT_EDT_HAlignMask |
443                                      FWL_STYLEEXT_EDT_HAlignModeMask |
444                                      FWL_STYLEEXT_EDT_VAlignMask);
445 }
446 
ResetListItemAlignment()447 void CFWL_ComboBox::ResetListItemAlignment() {
448   if (!m_pListBox)
449     return;
450 
451   uint32_t dwAdd = 0;
452   switch (m_pProperties->m_dwStyleExes & FWL_STYLEEXT_CMB_ListItemAlignMask) {
453     case FWL_STYLEEXT_CMB_ListItemCenterAlign: {
454       dwAdd |= FWL_STYLEEXT_LTB_CenterAlign;
455       break;
456     }
457     default: {
458       dwAdd |= FWL_STYLEEXT_LTB_LeftAlign;
459       break;
460     }
461   }
462   m_pListBox->ModifyStylesEx(dwAdd, FWL_STYLEEXT_CMB_ListItemAlignMask);
463 }
464 
ProcessSelChanged(bool bLButtonUp)465 void CFWL_ComboBox::ProcessSelChanged(bool bLButtonUp) {
466   m_iCurSel = m_pListBox->GetItemIndex(this, m_pListBox->GetSelItem(0));
467   if (!IsDropDownStyle()) {
468     RepaintRect(m_rtClient);
469     return;
470   }
471 
472   CFWL_ListItem* hItem = m_pListBox->GetItem(this, m_iCurSel);
473   if (!hItem)
474     return;
475   if (m_pEdit) {
476     m_pEdit->SetText(hItem->GetText());
477     m_pEdit->Update();
478     m_pEdit->SetSelected();
479   }
480 
481   CFWL_EventSelectChanged ev(this);
482   ev.bLButtonUp = bLButtonUp;
483   DispatchEvent(&ev);
484 }
485 
InitProxyForm()486 void CFWL_ComboBox::InitProxyForm() {
487   if (m_pComboBoxProxy)
488     return;
489   if (!m_pListBox)
490     return;
491 
492   auto prop = pdfium::MakeUnique<CFWL_WidgetProperties>();
493   prop->m_pOwner = this;
494   prop->m_dwStyles = FWL_WGTSTYLE_Popup;
495   prop->m_dwStates = FWL_WGTSTATE_Invisible;
496 
497   // TODO(dsinclair): Does this leak? I don't see a delete, but I'm not sure
498   // if the SetParent call is going to transfer ownership.
499   m_pComboBoxProxy = new CFWL_ComboBoxProxy(this, m_pOwnerApp, std::move(prop),
500                                             m_pListBox.get());
501   m_pListBox->SetParent(m_pComboBoxProxy);
502 }
503 
DisForm_InitComboList()504 void CFWL_ComboBox::DisForm_InitComboList() {
505   if (m_pListBox)
506     return;
507 
508   auto prop = pdfium::MakeUnique<CFWL_WidgetProperties>();
509   prop->m_pParent = this;
510   prop->m_dwStyles = FWL_WGTSTYLE_Border | FWL_WGTSTYLE_VScroll;
511   prop->m_dwStates = FWL_WGTSTATE_Invisible;
512   prop->m_pThemeProvider = m_pProperties->m_pThemeProvider;
513   m_pListBox =
514       pdfium::MakeUnique<CFWL_ComboList>(m_pOwnerApp, std::move(prop), this);
515 }
516 
DisForm_InitComboEdit()517 void CFWL_ComboBox::DisForm_InitComboEdit() {
518   if (m_pEdit)
519     return;
520 
521   auto prop = pdfium::MakeUnique<CFWL_WidgetProperties>();
522   prop->m_pParent = this;
523   prop->m_pThemeProvider = m_pProperties->m_pThemeProvider;
524 
525   m_pEdit =
526       pdfium::MakeUnique<CFWL_ComboEdit>(m_pOwnerApp, std::move(prop), this);
527   m_pEdit->SetOuter(this);
528 }
529 
DisForm_ShowDropList(bool bActivate)530 void CFWL_ComboBox::DisForm_ShowDropList(bool bActivate) {
531   if (DisForm_IsDropListVisible() == bActivate)
532     return;
533 
534   if (bActivate) {
535     CFWL_Event preEvent(CFWL_Event::Type::PreDropDown, this);
536     DispatchEvent(&preEvent);
537 
538     CFWL_ComboList* pComboList = m_pListBox.get();
539     int32_t iItems = pComboList->CountItems(nullptr);
540     if (iItems < 1)
541       return;
542 
543     ResetListItemAlignment();
544     pComboList->ChangeSelected(m_iCurSel);
545 
546     FX_FLOAT fItemHeight = pComboList->CalcItemHeight();
547     FX_FLOAT fBorder = GetBorderSize(true);
548     FX_FLOAT fPopupMin = 0.0f;
549     if (iItems > 3)
550       fPopupMin = fItemHeight * 3 + fBorder * 2;
551 
552     FX_FLOAT fPopupMax = fItemHeight * iItems + fBorder * 2;
553     CFX_RectF rtList(m_rtClient.left, 0, m_pProperties->m_rtWidget.width, 0);
554     GetPopupPos(fPopupMin, fPopupMax, m_pProperties->m_rtWidget, rtList);
555 
556     m_pListBox->SetWidgetRect(rtList);
557     m_pListBox->Update();
558   } else {
559     SetFocus(true);
560   }
561 
562   if (bActivate) {
563     m_pListBox->RemoveStates(FWL_WGTSTATE_Invisible);
564     CFWL_Event postEvent(CFWL_Event::Type::PostDropDown, this);
565     DispatchEvent(&postEvent);
566   } else {
567     m_pListBox->SetStates(FWL_WGTSTATE_Invisible);
568   }
569 
570   CFX_RectF rect = m_pListBox->GetWidgetRect();
571   rect.Inflate(2, 2);
572   RepaintRect(rect);
573 }
574 
DisForm_ModifyStylesEx(uint32_t dwStylesExAdded,uint32_t dwStylesExRemoved)575 void CFWL_ComboBox::DisForm_ModifyStylesEx(uint32_t dwStylesExAdded,
576                                            uint32_t dwStylesExRemoved) {
577   if (!m_pEdit)
578     DisForm_InitComboEdit();
579 
580   bool bAddDropDown = !!(dwStylesExAdded & FWL_STYLEEXT_CMB_DropDown);
581   bool bDelDropDown = !!(dwStylesExRemoved & FWL_STYLEEXT_CMB_DropDown);
582 
583   dwStylesExRemoved &= ~FWL_STYLEEXT_CMB_DropDown;
584   m_pProperties->m_dwStyleExes |= FWL_STYLEEXT_CMB_DropDown;
585 
586   if (bAddDropDown)
587     m_pEdit->ModifyStylesEx(0, FWL_STYLEEXT_EDT_ReadOnly);
588   else if (bDelDropDown)
589     m_pEdit->ModifyStylesEx(FWL_STYLEEXT_EDT_ReadOnly, 0);
590   CFWL_Widget::ModifyStylesEx(dwStylesExAdded, dwStylesExRemoved);
591 }
592 
DisForm_Update()593 void CFWL_ComboBox::DisForm_Update() {
594   if (m_iLock)
595     return;
596   if (m_pEdit)
597     ResetEditAlignment();
598   ResetTheme();
599   Layout();
600 }
601 
DisForm_HitTest(const CFX_PointF & point)602 FWL_WidgetHit CFWL_ComboBox::DisForm_HitTest(const CFX_PointF& point) {
603   CFX_RectF rect(0, 0, m_pProperties->m_rtWidget.width - m_rtBtn.width,
604                  m_pProperties->m_rtWidget.height);
605   if (rect.Contains(point))
606     return FWL_WidgetHit::Edit;
607   if (m_rtBtn.Contains(point))
608     return FWL_WidgetHit::Client;
609   if (DisForm_IsDropListVisible()) {
610     rect = m_pListBox->GetWidgetRect();
611     if (rect.Contains(point))
612       return FWL_WidgetHit::Client;
613   }
614   return FWL_WidgetHit::Unknown;
615 }
616 
DisForm_DrawWidget(CFX_Graphics * pGraphics,const CFX_Matrix * pMatrix)617 void CFWL_ComboBox::DisForm_DrawWidget(CFX_Graphics* pGraphics,
618                                        const CFX_Matrix* pMatrix) {
619   IFWL_ThemeProvider* pTheme = m_pProperties->m_pThemeProvider;
620   CFX_Matrix mtOrg(1, 0, 0, 1, 0, 0);
621   if (pMatrix)
622     mtOrg = *pMatrix;
623 
624   pGraphics->SaveGraphState();
625   pGraphics->ConcatMatrix(&mtOrg);
626   if (!m_rtBtn.IsEmpty(0.1f)) {
627     CFWL_ThemeBackground param;
628     param.m_pWidget = this;
629     param.m_iPart = CFWL_Part::DropDownButton;
630     param.m_dwStates = m_iBtnState;
631     param.m_pGraphics = pGraphics;
632     param.m_rtPart = m_rtBtn;
633     pTheme->DrawBackground(&param);
634   }
635   pGraphics->RestoreGraphState();
636 
637   if (m_pEdit) {
638     CFX_RectF rtEdit = m_pEdit->GetWidgetRect();
639     CFX_Matrix mt(1, 0, 0, 1, rtEdit.left, rtEdit.top);
640     mt.Concat(mtOrg);
641     m_pEdit->DrawWidget(pGraphics, &mt);
642   }
643   if (m_pListBox && DisForm_IsDropListVisible()) {
644     CFX_RectF rtList = m_pListBox->GetWidgetRect();
645     CFX_Matrix mt(1, 0, 0, 1, rtList.left, rtList.top);
646     mt.Concat(mtOrg);
647     m_pListBox->DrawWidget(pGraphics, &mt);
648   }
649 }
650 
DisForm_GetBBox() const651 CFX_RectF CFWL_ComboBox::DisForm_GetBBox() const {
652   CFX_RectF rect = m_pProperties->m_rtWidget;
653   if (!m_pListBox || !DisForm_IsDropListVisible())
654     return rect;
655 
656   CFX_RectF rtList = m_pListBox->GetWidgetRect();
657   rtList.Offset(rect.left, rect.top);
658   rect.Union(rtList);
659   return rect;
660 }
661 
DisForm_Layout()662 void CFWL_ComboBox::DisForm_Layout() {
663   m_rtClient = GetClientRect();
664   m_rtContent = m_rtClient;
665   IFWL_ThemeProvider* theme = GetAvailableTheme();
666   if (!theme)
667     return;
668 
669   FX_FLOAT borderWidth = 1;
670   FX_FLOAT fBtn = theme->GetScrollBarWidth();
671   if (!(GetStylesEx() & FWL_STYLEEXT_CMB_ReadOnly)) {
672     m_rtBtn =
673         CFX_RectF(m_rtClient.right() - fBtn, m_rtClient.top + borderWidth,
674                   fBtn - borderWidth, m_rtClient.height - 2 * borderWidth);
675   }
676 
677   CFWL_ThemePart part;
678   part.m_pWidget = this;
679   CFX_RectF pUIMargin = theme->GetUIMargin(&part);
680   m_rtContent.Deflate(pUIMargin.left, pUIMargin.top, pUIMargin.width,
681                       pUIMargin.height);
682 
683   if (!IsDropDownStyle() || !m_pEdit)
684     return;
685 
686   CFX_RectF rtEdit(m_rtContent.left, m_rtContent.top, m_rtContent.width - fBtn,
687                    m_rtContent.height);
688   m_pEdit->SetWidgetRect(rtEdit);
689 
690   if (m_iCurSel >= 0) {
691     CFWL_ListItem* hItem = m_pListBox->GetItem(this, m_iCurSel);
692     m_pEdit->LockUpdate();
693     m_pEdit->SetText(hItem ? hItem->GetText() : L"");
694     m_pEdit->UnlockUpdate();
695   }
696   m_pEdit->Update();
697 }
698 
OnProcessMessage(CFWL_Message * pMessage)699 void CFWL_ComboBox::OnProcessMessage(CFWL_Message* pMessage) {
700   if (m_pWidgetMgr->IsFormDisabled()) {
701     DisForm_OnProcessMessage(pMessage);
702     return;
703   }
704   if (!pMessage)
705     return;
706 
707   switch (pMessage->GetType()) {
708     case CFWL_Message::Type::SetFocus:
709       OnFocusChanged(pMessage, true);
710       break;
711     case CFWL_Message::Type::KillFocus:
712       OnFocusChanged(pMessage, false);
713       break;
714     case CFWL_Message::Type::Mouse: {
715       CFWL_MessageMouse* pMsg = static_cast<CFWL_MessageMouse*>(pMessage);
716       switch (pMsg->m_dwCmd) {
717         case FWL_MouseCommand::LeftButtonDown:
718           OnLButtonDown(pMsg);
719           break;
720         case FWL_MouseCommand::LeftButtonUp:
721           OnLButtonUp(pMsg);
722           break;
723         case FWL_MouseCommand::Move:
724           OnMouseMove(pMsg);
725           break;
726         case FWL_MouseCommand::Leave:
727           OnMouseLeave(pMsg);
728           break;
729         default:
730           break;
731       }
732       break;
733     }
734     case CFWL_Message::Type::Key:
735       OnKey(static_cast<CFWL_MessageKey*>(pMessage));
736       break;
737     default:
738       break;
739   }
740 
741   CFWL_Widget::OnProcessMessage(pMessage);
742 }
743 
OnProcessEvent(CFWL_Event * pEvent)744 void CFWL_ComboBox::OnProcessEvent(CFWL_Event* pEvent) {
745   CFWL_Event::Type type = pEvent->GetType();
746   if (type == CFWL_Event::Type::Scroll) {
747     CFWL_EventScroll* pScrollEvent = static_cast<CFWL_EventScroll*>(pEvent);
748     CFWL_EventScroll pScrollEv(this);
749     pScrollEv.m_iScrollCode = pScrollEvent->m_iScrollCode;
750     pScrollEv.m_fPos = pScrollEvent->m_fPos;
751     DispatchEvent(&pScrollEv);
752   } else if (type == CFWL_Event::Type::TextChanged) {
753     CFWL_Event pTemp(CFWL_Event::Type::EditChanged, this);
754     DispatchEvent(&pTemp);
755   }
756 }
757 
OnDrawWidget(CFX_Graphics * pGraphics,const CFX_Matrix * pMatrix)758 void CFWL_ComboBox::OnDrawWidget(CFX_Graphics* pGraphics,
759                                  const CFX_Matrix* pMatrix) {
760   DrawWidget(pGraphics, pMatrix);
761 }
762 
OnFocusChanged(CFWL_Message * pMsg,bool bSet)763 void CFWL_ComboBox::OnFocusChanged(CFWL_Message* pMsg, bool bSet) {
764   if (bSet) {
765     m_pProperties->m_dwStates |= FWL_WGTSTATE_Focused;
766     if (IsDropDownStyle() && pMsg->m_pSrcTarget != m_pListBox.get()) {
767       if (!m_pEdit)
768         return;
769       m_pEdit->SetSelected();
770       return;
771     }
772 
773     RepaintRect(m_rtClient);
774     return;
775   }
776 
777   m_pProperties->m_dwStates &= ~FWL_WGTSTATE_Focused;
778   if (!IsDropDownStyle() || pMsg->m_pDstTarget == m_pListBox.get()) {
779     RepaintRect(m_rtClient);
780     return;
781   }
782   if (!m_pEdit)
783     return;
784 
785   m_pEdit->FlagFocus(false);
786   m_pEdit->ClearSelected();
787 }
788 
OnLButtonDown(CFWL_MessageMouse * pMsg)789 void CFWL_ComboBox::OnLButtonDown(CFWL_MessageMouse* pMsg) {
790   if (m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled)
791     return;
792 
793   CFX_RectF& rtBtn = IsDropDownStyle() ? m_rtBtn : m_rtClient;
794   if (!rtBtn.Contains(pMsg->m_pos))
795     return;
796 
797   if (IsDropDownStyle() && m_pEdit)
798     MatchEditText();
799 
800   m_bLButtonDown = true;
801   m_iBtnState = CFWL_PartState_Pressed;
802   RepaintRect(m_rtClient);
803 
804   ShowDropList(true);
805   m_iBtnState = CFWL_PartState_Normal;
806   RepaintRect(m_rtClient);
807 }
808 
OnLButtonUp(CFWL_MessageMouse * pMsg)809 void CFWL_ComboBox::OnLButtonUp(CFWL_MessageMouse* pMsg) {
810   m_bLButtonDown = false;
811   if (m_rtBtn.Contains(pMsg->m_pos))
812     m_iBtnState = CFWL_PartState_Hovered;
813   else
814     m_iBtnState = CFWL_PartState_Normal;
815 
816   RepaintRect(m_rtBtn);
817 }
818 
OnMouseMove(CFWL_MessageMouse * pMsg)819 void CFWL_ComboBox::OnMouseMove(CFWL_MessageMouse* pMsg) {
820   int32_t iOldState = m_iBtnState;
821   if (m_rtBtn.Contains(pMsg->m_pos)) {
822     m_iBtnState =
823         m_bLButtonDown ? CFWL_PartState_Pressed : CFWL_PartState_Hovered;
824   } else {
825     m_iBtnState = CFWL_PartState_Normal;
826   }
827   if ((iOldState != m_iBtnState) &&
828       !((m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled) ==
829         FWL_WGTSTATE_Disabled)) {
830     RepaintRect(m_rtBtn);
831   }
832 }
833 
OnMouseLeave(CFWL_MessageMouse * pMsg)834 void CFWL_ComboBox::OnMouseLeave(CFWL_MessageMouse* pMsg) {
835   if (!IsDropListVisible() &&
836       !((m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled) ==
837         FWL_WGTSTATE_Disabled)) {
838     m_iBtnState = CFWL_PartState_Normal;
839     RepaintRect(m_rtBtn);
840   }
841 }
842 
OnKey(CFWL_MessageKey * pMsg)843 void CFWL_ComboBox::OnKey(CFWL_MessageKey* pMsg) {
844   uint32_t dwKeyCode = pMsg->m_dwKeyCode;
845   if (dwKeyCode == FWL_VKEY_Tab)
846     return;
847   if (pMsg->m_pDstTarget == this)
848     DoSubCtrlKey(pMsg);
849 }
850 
DoSubCtrlKey(CFWL_MessageKey * pMsg)851 void CFWL_ComboBox::DoSubCtrlKey(CFWL_MessageKey* pMsg) {
852   uint32_t dwKeyCode = pMsg->m_dwKeyCode;
853   const bool bUp = dwKeyCode == FWL_VKEY_Up;
854   const bool bDown = dwKeyCode == FWL_VKEY_Down;
855   if (bUp || bDown) {
856     int32_t iCount = m_pListBox->CountItems(nullptr);
857     if (iCount < 1)
858       return;
859 
860     bool bMatchEqual = false;
861     int32_t iCurSel = m_iCurSel;
862     bool bDropDown = IsDropDownStyle();
863     if (bDropDown && m_pEdit) {
864       CFX_WideString wsText = m_pEdit->GetText();
865       iCurSel = m_pListBox->MatchItem(wsText);
866       if (iCurSel >= 0) {
867         CFWL_ListItem* hItem = m_pListBox->GetItem(this, iCurSel);
868         bMatchEqual = wsText == (hItem ? hItem->GetText() : L"");
869       }
870     }
871     if (iCurSel < 0) {
872       iCurSel = 0;
873     } else if (!bDropDown || bMatchEqual) {
874       if ((bUp && iCurSel == 0) || (bDown && iCurSel == iCount - 1))
875         return;
876       if (bUp)
877         iCurSel--;
878       else
879         iCurSel++;
880     }
881     m_iCurSel = iCurSel;
882     if (bDropDown && m_pEdit)
883       SyncEditText(m_iCurSel);
884     else
885       RepaintRect(m_rtClient);
886     return;
887   }
888 
889   if (IsDropDownStyle())
890     m_pEdit->GetDelegate()->OnProcessMessage(pMsg);
891 }
892 
DisForm_OnProcessMessage(CFWL_Message * pMessage)893 void CFWL_ComboBox::DisForm_OnProcessMessage(CFWL_Message* pMessage) {
894   if (!pMessage)
895     return;
896 
897   bool backDefault = true;
898   switch (pMessage->GetType()) {
899     case CFWL_Message::Type::SetFocus: {
900       backDefault = false;
901       DisForm_OnFocusChanged(pMessage, true);
902       break;
903     }
904     case CFWL_Message::Type::KillFocus: {
905       backDefault = false;
906       DisForm_OnFocusChanged(pMessage, false);
907       break;
908     }
909     case CFWL_Message::Type::Mouse: {
910       backDefault = false;
911       CFWL_MessageMouse* pMsg = static_cast<CFWL_MessageMouse*>(pMessage);
912       switch (pMsg->m_dwCmd) {
913         case FWL_MouseCommand::LeftButtonDown:
914           DisForm_OnLButtonDown(pMsg);
915           break;
916         case FWL_MouseCommand::LeftButtonUp:
917           OnLButtonUp(pMsg);
918           break;
919         default:
920           break;
921       }
922       break;
923     }
924     case CFWL_Message::Type::Key: {
925       backDefault = false;
926       CFWL_MessageKey* pKey = static_cast<CFWL_MessageKey*>(pMessage);
927       if (pKey->m_dwCmd == FWL_KeyCommand::KeyUp)
928         break;
929       if (DisForm_IsDropListVisible() &&
930           pKey->m_dwCmd == FWL_KeyCommand::KeyDown) {
931         bool bListKey = pKey->m_dwKeyCode == FWL_VKEY_Up ||
932                         pKey->m_dwKeyCode == FWL_VKEY_Down ||
933                         pKey->m_dwKeyCode == FWL_VKEY_Return ||
934                         pKey->m_dwKeyCode == FWL_VKEY_Escape;
935         if (bListKey) {
936           m_pListBox->GetDelegate()->OnProcessMessage(pMessage);
937           break;
938         }
939       }
940       DisForm_OnKey(pKey);
941       break;
942     }
943     default:
944       break;
945   }
946   if (backDefault)
947     CFWL_Widget::OnProcessMessage(pMessage);
948 }
949 
DisForm_OnLButtonDown(CFWL_MessageMouse * pMsg)950 void CFWL_ComboBox::DisForm_OnLButtonDown(CFWL_MessageMouse* pMsg) {
951   bool bDropDown = DisForm_IsDropListVisible();
952   CFX_RectF& rtBtn = bDropDown ? m_rtBtn : m_rtClient;
953   if (!rtBtn.Contains(pMsg->m_pos))
954     return;
955 
956   if (DisForm_IsDropListVisible()) {
957     DisForm_ShowDropList(false);
958     return;
959   }
960   if (m_pEdit)
961     MatchEditText();
962   DisForm_ShowDropList(true);
963 }
964 
DisForm_OnFocusChanged(CFWL_Message * pMsg,bool bSet)965 void CFWL_ComboBox::DisForm_OnFocusChanged(CFWL_Message* pMsg, bool bSet) {
966   if (bSet) {
967     m_pProperties->m_dwStates |= FWL_WGTSTATE_Focused;
968     if ((m_pEdit->GetStates() & FWL_WGTSTATE_Focused) == 0) {
969       CFWL_MessageSetFocus msg(nullptr, m_pEdit.get());
970       m_pEdit->GetDelegate()->OnProcessMessage(&msg);
971     }
972   } else {
973     m_pProperties->m_dwStates &= ~FWL_WGTSTATE_Focused;
974     DisForm_ShowDropList(false);
975     CFWL_MessageKillFocus msg(m_pEdit.get());
976     m_pEdit->GetDelegate()->OnProcessMessage(&msg);
977   }
978 }
979 
DisForm_OnKey(CFWL_MessageKey * pMsg)980 void CFWL_ComboBox::DisForm_OnKey(CFWL_MessageKey* pMsg) {
981   uint32_t dwKeyCode = pMsg->m_dwKeyCode;
982   const bool bUp = dwKeyCode == FWL_VKEY_Up;
983   const bool bDown = dwKeyCode == FWL_VKEY_Down;
984   if (bUp || bDown) {
985     CFWL_ComboList* pComboList = m_pListBox.get();
986     int32_t iCount = pComboList->CountItems(nullptr);
987     if (iCount < 1)
988       return;
989 
990     bool bMatchEqual = false;
991     int32_t iCurSel = m_iCurSel;
992     if (m_pEdit) {
993       CFX_WideString wsText = m_pEdit->GetText();
994       iCurSel = pComboList->MatchItem(wsText);
995       if (iCurSel >= 0) {
996         CFWL_ListItem* item = m_pListBox->GetSelItem(iCurSel);
997         bMatchEqual = wsText == (item ? item->GetText() : L"");
998       }
999     }
1000     if (iCurSel < 0) {
1001       iCurSel = 0;
1002     } else if (bMatchEqual) {
1003       if ((bUp && iCurSel == 0) || (bDown && iCurSel == iCount - 1))
1004         return;
1005       if (bUp)
1006         iCurSel--;
1007       else
1008         iCurSel++;
1009     }
1010     m_iCurSel = iCurSel;
1011     SyncEditText(m_iCurSel);
1012     return;
1013   }
1014   if (m_pEdit)
1015     m_pEdit->GetDelegate()->OnProcessMessage(pMsg);
1016 }
1017