Home | History | Annotate | Download | only in fwl
      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_texteditengine.h"
     15 #include "xfa/fde/cfde_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 
     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 = pdfium::MakeUnique<CFWL_ComboList>(m_pOwnerApp.Get(),
     53                                                   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.Get(), 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 
     66 CFWL_ComboBox::~CFWL_ComboBox() {}
     67 
     68 FWL_Type CFWL_ComboBox::GetClassID() const {
     69   return FWL_Type::ComboBox;
     70 }
     71 
     72 void CFWL_ComboBox::AddString(const WideStringView& wsText) {
     73   m_pListBox->AddString(wsText);
     74 }
     75 
     76 void CFWL_ComboBox::RemoveAt(int32_t iIndex) {
     77   m_pListBox->RemoveAt(iIndex);
     78 }
     79 
     80 void CFWL_ComboBox::RemoveAll() {
     81   m_pListBox->DeleteAll();
     82 }
     83 
     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.Get(), pdfium::MakeUnique<CFWL_WidgetProperties>(),
     96         nullptr);
     97     m_pEdit->SetOuter(this);
     98     m_pEdit->SetParent(this);
     99   } else if (bRemoveDropDown && m_pEdit) {
    100     m_pEdit->SetStates(FWL_WGTSTATE_Invisible);
    101   }
    102   CFWL_Widget::ModifyStylesEx(dwStylesExAdded, dwStylesExRemoved);
    103 }
    104 
    105 void CFWL_ComboBox::Update() {
    106   if (m_pWidgetMgr->IsFormDisabled()) {
    107     DisForm_Update();
    108     return;
    109   }
    110   if (IsLocked())
    111     return;
    112 
    113   ResetTheme();
    114   if (IsDropDownStyle() && m_pEdit)
    115     ResetEditAlignment();
    116   if (!m_pProperties->m_pThemeProvider)
    117     m_pProperties->m_pThemeProvider = GetAvailableTheme();
    118 
    119   Layout();
    120 }
    121 
    122 FWL_WidgetHit CFWL_ComboBox::HitTest(const CFX_PointF& point) {
    123   if (m_pWidgetMgr->IsFormDisabled())
    124     return DisForm_HitTest(point);
    125   return CFWL_Widget::HitTest(point);
    126 }
    127 
    128 void CFWL_ComboBox::DrawWidget(CXFA_Graphics* pGraphics,
    129                                const CFX_Matrix& matrix) {
    130   if (m_pWidgetMgr->IsFormDisabled()) {
    131     DisForm_DrawWidget(pGraphics, &matrix);
    132     return;
    133   }
    134 
    135   if (!pGraphics)
    136     return;
    137   if (!m_pProperties->m_pThemeProvider)
    138     return;
    139 
    140   IFWL_ThemeProvider* pTheme = m_pProperties->m_pThemeProvider;
    141   if (HasBorder())
    142     DrawBorder(pGraphics, CFWL_Part::Border, pTheme, matrix);
    143 
    144   if (!IsDropDownStyle()) {
    145     CFX_RectF rtTextBk(m_rtClient);
    146     rtTextBk.width -= m_rtBtn.width;
    147 
    148     CFWL_ThemeBackground param;
    149     param.m_pWidget = this;
    150     param.m_iPart = CFWL_Part::Background;
    151     param.m_pGraphics = pGraphics;
    152     param.m_matrix.Concat(matrix);
    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(matrix);
    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.single_line_ = true;
    183       theme_text.m_iTTOAlign = FDE_TextAlignment::kCenterLeft;
    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(matrix);
    196   param.m_rtPart = m_rtBtn;
    197   pTheme->DrawBackground(&param);
    198 }
    199 
    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 
    211 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 
    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(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 
    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 
    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 
    248 void CFWL_ComboBox::SetEditText(const WideString& wsText) {
    249   if (!m_pEdit)
    250     return;
    251 
    252   m_pEdit->SetText(wsText);
    253   m_pEdit->Update();
    254 }
    255 
    256 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 
    266 void CFWL_ComboBox::OpenDropDownList(bool bActivate) {
    267   ShowDropList(bActivate);
    268 }
    269 
    270 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 
    284 void CFWL_ComboBox::EditModifyStylesEx(uint32_t dwStylesExAdded,
    285                                        uint32_t dwStylesExRemoved) {
    286   if (m_pEdit)
    287     m_pEdit->ModifyStylesEx(dwStylesExAdded, dwStylesExRemoved);
    288 }
    289 
    290 void CFWL_ComboBox::DrawStretchHandler(CXFA_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 
    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 
    350 void CFWL_ComboBox::MatchEditText() {
    351   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 
    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 
    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   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 
    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 
    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 
    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 
    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 
    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.Get(),
    500                                             std::move(prop), m_pListBox.get());
    501   m_pListBox->SetParent(m_pComboBoxProxy);
    502 }
    503 
    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 = pdfium::MakeUnique<CFWL_ComboList>(m_pOwnerApp.Get(),
    514                                                   std::move(prop), this);
    515 }
    516 
    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 = pdfium::MakeUnique<CFWL_ComboEdit>(m_pOwnerApp.Get(),
    526                                                std::move(prop), this);
    527   m_pEdit->SetOuter(this);
    528 }
    529 
    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     float fItemHeight = pComboList->CalcItemHeight();
    547     float fBorder = GetBorderSize(true);
    548     float fPopupMin = 0.0f;
    549     if (iItems > 3)
    550       fPopupMin = fItemHeight * 3 + fBorder * 2;
    551 
    552     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 
    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 
    593 void CFWL_ComboBox::DisForm_Update() {
    594   if (m_iLock)
    595     return;
    596   if (m_pEdit)
    597     ResetEditAlignment();
    598   ResetTheme();
    599   Layout();
    600 }
    601 
    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 
    617 void CFWL_ComboBox::DisForm_DrawWidget(CXFA_Graphics* pGraphics,
    618                                        const CFX_Matrix* pMatrix) {
    619   IFWL_ThemeProvider* pTheme = m_pProperties->m_pThemeProvider;
    620   CFX_Matrix mtOrg;
    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 
    651 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 
    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   float borderWidth = 1;
    670   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 
    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 
    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 
    758 void CFWL_ComboBox::OnDrawWidget(CXFA_Graphics* pGraphics,
    759                                  const CFX_Matrix& matrix) {
    760   DrawWidget(pGraphics, matrix);
    761 }
    762 
    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 
    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 
    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 
    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 
    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 
    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 
    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       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 
    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 
    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 
    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 
    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       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