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(¶m);
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(¶m);
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(¶m);
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(¶m);
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