cfwl_combobox.cpp revision 5ae9d0c6fd838a2967cca72aa5751b51dadc2769
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
33CFWL_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
66CFWL_ComboBox::~CFWL_ComboBox() {}
67
68FWL_Type CFWL_ComboBox::GetClassID() const {
69  return FWL_Type::ComboBox;
70}
71
72void CFWL_ComboBox::AddString(const CFX_WideStringC& wsText) {
73  m_pListBox->AddString(wsText);
74}
75
76void CFWL_ComboBox::RemoveAt(int32_t iIndex) {
77  m_pListBox->RemoveAt(iIndex);
78}
79
80void CFWL_ComboBox::RemoveAll() {
81  m_pListBox->DeleteAll();
82}
83
84void 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
104void 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
121FWL_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
127void 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
200void 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
211CFX_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
217void 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
232void 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
240void 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
248void 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
256CFX_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
266void CFWL_ComboBox::OpenDropDownList(bool bActivate) {
267  ShowDropList(bActivate);
268}
269
270CFX_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
284void CFWL_ComboBox::EditModifyStylesEx(uint32_t dwStylesExAdded,
285                                       uint32_t dwStylesExRemoved) {
286  if (m_pEdit)
287    m_pEdit->ModifyStylesEx(dwStylesExAdded, dwStylesExRemoved);
288}
289
290void 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
303void 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
350void 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
363void 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
370void 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
398void 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
410void 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
447void 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
465void 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
486void 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
504void 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
517void 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
530void 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
575void 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
593void CFWL_ComboBox::DisForm_Update() {
594  if (m_iLock)
595    return;
596  if (m_pEdit)
597    ResetEditAlignment();
598  ResetTheme();
599  Layout();
600}
601
602FWL_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
617void 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
651CFX_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
662void 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
699void 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
744void 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
758void CFWL_ComboBox::OnDrawWidget(CFX_Graphics* pGraphics,
759                                 const CFX_Matrix* pMatrix) {
760  DrawWidget(pGraphics, pMatrix);
761}
762
763void 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
789void 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
809void 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
819void 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
834void 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
843void 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
851void 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
893void 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
950void 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
965void 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
980void 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