1 // Copyright 2014 The PDFium Authors
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 "v8/include/cppgc/visitor.h"
10 #include "xfa/fde/cfde_texteditengine.h"
11 #include "xfa/fde/cfde_textout.h"
12 #include "xfa/fwl/cfwl_app.h"
13 #include "xfa/fwl/cfwl_event.h"
14 #include "xfa/fwl/cfwl_eventselectchanged.h"
15 #include "xfa/fwl/cfwl_listbox.h"
16 #include "xfa/fwl/cfwl_messagekey.h"
17 #include "xfa/fwl/cfwl_messagekillfocus.h"
18 #include "xfa/fwl/cfwl_messagemouse.h"
19 #include "xfa/fwl/cfwl_messagesetfocus.h"
20 #include "xfa/fwl/cfwl_notedriver.h"
21 #include "xfa/fwl/cfwl_themebackground.h"
22 #include "xfa/fwl/cfwl_themepart.h"
23 #include "xfa/fwl/cfwl_themetext.h"
24 #include "xfa/fwl/cfwl_widgetmgr.h"
25 #include "xfa/fwl/fwl_widgetdef.h"
26 #include "xfa/fwl/ifwl_themeprovider.h"
27
CFWL_ComboBox(CFWL_App * app)28 CFWL_ComboBox::CFWL_ComboBox(CFWL_App* app)
29 : CFWL_Widget(app, Properties(), nullptr),
30 m_pEdit(cppgc::MakeGarbageCollected<CFWL_ComboEdit>(
31 app->GetHeap()->GetAllocationHandle(),
32 app,
33 Properties(),
34 this)),
35 m_pListBox(cppgc::MakeGarbageCollected<CFWL_ComboList>(
36 app->GetHeap()->GetAllocationHandle(),
37 app,
38 Properties{FWL_STYLE_WGT_Border | FWL_STYLE_WGT_VScroll, 0,
39 FWL_STATE_WGT_Invisible},
40 this)) {}
41
42 CFWL_ComboBox::~CFWL_ComboBox() = default;
43
Trace(cppgc::Visitor * visitor) const44 void CFWL_ComboBox::Trace(cppgc::Visitor* visitor) const {
45 CFWL_Widget::Trace(visitor);
46 visitor->Trace(m_pEdit);
47 visitor->Trace(m_pListBox);
48 }
49
GetClassID() const50 FWL_Type CFWL_ComboBox::GetClassID() const {
51 return FWL_Type::ComboBox;
52 }
53
AddString(const WideString & wsText)54 void CFWL_ComboBox::AddString(const WideString& wsText) {
55 m_pListBox->AddString(wsText);
56 }
57
RemoveAt(int32_t iIndex)58 void CFWL_ComboBox::RemoveAt(int32_t iIndex) {
59 m_pListBox->RemoveAt(iIndex);
60 }
61
RemoveAll()62 void CFWL_ComboBox::RemoveAll() {
63 m_pListBox->DeleteAll();
64 }
65
ModifyStyleExts(uint32_t dwStyleExtsAdded,uint32_t dwStyleExtsRemoved)66 void CFWL_ComboBox::ModifyStyleExts(uint32_t dwStyleExtsAdded,
67 uint32_t dwStyleExtsRemoved) {
68 bool bAddDropDown = !!(dwStyleExtsAdded & FWL_STYLEEXT_CMB_DropDown);
69 bool bDelDropDown = !!(dwStyleExtsRemoved & FWL_STYLEEXT_CMB_DropDown);
70 dwStyleExtsRemoved &= ~FWL_STYLEEXT_CMB_DropDown;
71 m_Properties.m_dwStyleExts |= FWL_STYLEEXT_CMB_DropDown;
72 if (bAddDropDown)
73 m_pEdit->ModifyStyleExts(0, FWL_STYLEEXT_EDT_ReadOnly);
74 else if (bDelDropDown)
75 m_pEdit->ModifyStyleExts(FWL_STYLEEXT_EDT_ReadOnly, 0);
76
77 CFWL_Widget::ModifyStyleExts(dwStyleExtsAdded, dwStyleExtsRemoved);
78 }
79
Update()80 void CFWL_ComboBox::Update() {
81 if (IsLocked())
82 return;
83
84 if (m_pEdit)
85 ResetEditAlignment();
86 Layout();
87 }
88
HitTest(const CFX_PointF & point)89 FWL_WidgetHit CFWL_ComboBox::HitTest(const CFX_PointF& point) {
90 CFX_RectF rect(0, 0, m_WidgetRect.width - m_BtnRect.width,
91 m_WidgetRect.height);
92 if (rect.Contains(point))
93 return FWL_WidgetHit::Edit;
94 if (m_BtnRect.Contains(point))
95 return FWL_WidgetHit::Client;
96 if (IsDropListVisible()) {
97 rect = m_pListBox->GetWidgetRect();
98 if (rect.Contains(point))
99 return FWL_WidgetHit::Client;
100 }
101 return FWL_WidgetHit::Unknown;
102 }
103
DrawWidget(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & matrix)104 void CFWL_ComboBox::DrawWidget(CFGAS_GEGraphics* pGraphics,
105 const CFX_Matrix& matrix) {
106 if (!m_BtnRect.IsEmpty(0.1f)) {
107 CFGAS_GEGraphics::StateRestorer restorer(pGraphics);
108 pGraphics->ConcatMatrix(matrix);
109 CFWL_ThemeBackground param(CFWL_ThemePart::Part::kDropDownButton, this,
110 pGraphics);
111 param.m_dwStates = m_iBtnState;
112 param.m_PartRect = m_BtnRect;
113 GetThemeProvider()->DrawBackground(param);
114 }
115 if (m_pEdit) {
116 CFX_RectF rtEdit = m_pEdit->GetWidgetRect();
117 CFX_Matrix mt(1, 0, 0, 1, rtEdit.left, rtEdit.top);
118 mt.Concat(matrix);
119 m_pEdit->DrawWidget(pGraphics, mt);
120 }
121 if (m_pListBox && IsDropListVisible()) {
122 CFX_RectF rtList = m_pListBox->GetWidgetRect();
123 CFX_Matrix mt(1, 0, 0, 1, rtList.left, rtList.top);
124 mt.Concat(matrix);
125 m_pListBox->DrawWidget(pGraphics, mt);
126 }
127 }
128
GetTextByIndex(int32_t iIndex) const129 WideString CFWL_ComboBox::GetTextByIndex(int32_t iIndex) const {
130 CFWL_ListBox::Item* pItem = m_pListBox->GetItem(m_pListBox, iIndex);
131 return pItem ? pItem->GetText() : WideString();
132 }
133
SetCurSel(int32_t iSel)134 void CFWL_ComboBox::SetCurSel(int32_t iSel) {
135 int32_t iCount = m_pListBox->CountItems(nullptr);
136 bool bClearSel = iSel < 0 || iSel >= iCount;
137 if (IsDropDownStyle() && m_pEdit) {
138 if (bClearSel) {
139 m_pEdit->SetText(WideString());
140 } else {
141 CFWL_ListBox::Item* hItem = m_pListBox->GetItem(this, iSel);
142 m_pEdit->SetText(hItem ? hItem->GetText() : WideString());
143 }
144 m_pEdit->Update();
145 }
146 m_iCurSel = bClearSel ? -1 : iSel;
147 }
148
SetStates(uint32_t dwStates)149 void CFWL_ComboBox::SetStates(uint32_t dwStates) {
150 if (IsDropDownStyle() && m_pEdit)
151 m_pEdit->SetStates(dwStates);
152 if (m_pListBox)
153 m_pListBox->SetStates(dwStates);
154 CFWL_Widget::SetStates(dwStates);
155 }
156
RemoveStates(uint32_t dwStates)157 void CFWL_ComboBox::RemoveStates(uint32_t dwStates) {
158 if (IsDropDownStyle() && m_pEdit)
159 m_pEdit->RemoveStates(dwStates);
160 if (m_pListBox)
161 m_pListBox->RemoveStates(dwStates);
162 CFWL_Widget::RemoveStates(dwStates);
163 }
164
SetEditText(const WideString & wsText)165 void CFWL_ComboBox::SetEditText(const WideString& wsText) {
166 if (!m_pEdit)
167 return;
168
169 m_pEdit->SetText(wsText);
170 m_pEdit->Update();
171 }
172
GetEditText() const173 WideString CFWL_ComboBox::GetEditText() const {
174 if (m_pEdit)
175 return m_pEdit->GetText();
176 if (!m_pListBox)
177 return WideString();
178
179 CFWL_ListBox::Item* hItem = m_pListBox->GetItem(this, m_iCurSel);
180 return hItem ? hItem->GetText() : WideString();
181 }
182
GetBBox() const183 CFX_RectF CFWL_ComboBox::GetBBox() const {
184 CFX_RectF rect = m_WidgetRect;
185 if (!m_pListBox || !IsDropListVisible())
186 return rect;
187
188 CFX_RectF rtList = m_pListBox->GetWidgetRect();
189 rtList.Offset(rect.left, rect.top);
190 rect.Union(rtList);
191 return rect;
192 }
193
EditModifyStyleExts(uint32_t dwStyleExtsAdded,uint32_t dwStyleExtsRemoved)194 void CFWL_ComboBox::EditModifyStyleExts(uint32_t dwStyleExtsAdded,
195 uint32_t dwStyleExtsRemoved) {
196 if (m_pEdit)
197 m_pEdit->ModifyStyleExts(dwStyleExtsAdded, dwStyleExtsRemoved);
198 }
199
ShowDropDownList()200 void CFWL_ComboBox::ShowDropDownList() {
201 if (IsDropListVisible())
202 return;
203
204 CFWL_Event preEvent(CFWL_Event::Type::PreDropDown, this);
205 DispatchEvent(&preEvent);
206 if (!preEvent.GetSrcTarget())
207 return;
208
209 CFWL_ComboList* pComboList = m_pListBox;
210 int32_t iItems = pComboList->CountItems(nullptr);
211 if (iItems < 1)
212 return;
213
214 ResetListItemAlignment();
215 pComboList->ChangeSelected(m_iCurSel);
216
217 float fItemHeight = pComboList->CalcItemHeight();
218 float fBorder = GetCXBorderSize();
219 float fPopupMin = 0.0f;
220 if (iItems > 3)
221 fPopupMin = fItemHeight * 3 + fBorder * 2;
222
223 float fPopupMax = fItemHeight * iItems + fBorder * 2;
224 CFX_RectF rtList(m_ClientRect.left, 0, m_WidgetRect.width, 0);
225 GetPopupPos(fPopupMin, fPopupMax, m_WidgetRect, &rtList);
226 m_pListBox->SetWidgetRect(rtList);
227 m_pListBox->Update();
228 m_pListBox->RemoveStates(FWL_STATE_WGT_Invisible);
229
230 CFWL_Event postEvent(CFWL_Event::Type::PostDropDown, this);
231 DispatchEvent(&postEvent);
232 RepaintInflatedListBoxRect();
233 }
234
HideDropDownList()235 void CFWL_ComboBox::HideDropDownList() {
236 if (!IsDropListVisible())
237 return;
238
239 m_pListBox->SetStates(FWL_STATE_WGT_Invisible);
240 RepaintInflatedListBoxRect();
241 }
242
RepaintInflatedListBoxRect()243 void CFWL_ComboBox::RepaintInflatedListBoxRect() {
244 CFX_RectF rect = m_pListBox->GetWidgetRect();
245 rect.Inflate(2, 2);
246 RepaintRect(rect);
247 }
248
MatchEditText()249 void CFWL_ComboBox::MatchEditText() {
250 WideString wsText = m_pEdit->GetText();
251 int32_t iMatch = m_pListBox->MatchItem(wsText.AsStringView());
252 if (iMatch != m_iCurSel) {
253 m_pListBox->ChangeSelected(iMatch);
254 if (iMatch >= 0)
255 SyncEditText(iMatch);
256 } else if (iMatch >= 0) {
257 m_pEdit->SetSelected();
258 }
259 m_iCurSel = iMatch;
260 }
261
SyncEditText(int32_t iListItem)262 void CFWL_ComboBox::SyncEditText(int32_t iListItem) {
263 CFWL_ListBox::Item* hItem = m_pListBox->GetItem(this, iListItem);
264 m_pEdit->SetText(hItem ? hItem->GetText() : WideString());
265 m_pEdit->Update();
266 m_pEdit->SetSelected();
267 }
268
Layout()269 void CFWL_ComboBox::Layout() {
270 m_ClientRect = GetClientRect();
271 m_ContentRect = m_ClientRect;
272
273 IFWL_ThemeProvider* theme = GetThemeProvider();
274 float borderWidth = 1;
275 float fBtn = theme->GetScrollBarWidth();
276 if (!(GetStyleExts() & FWL_STYLEEXT_CMB_ReadOnly)) {
277 m_BtnRect =
278 CFX_RectF(m_ClientRect.right() - fBtn, m_ClientRect.top + borderWidth,
279 fBtn - borderWidth, m_ClientRect.height - 2 * borderWidth);
280 }
281
282 CFWL_ThemePart part(CFWL_ThemePart::Part::kNone, this);
283 CFX_RectF pUIMargin = theme->GetUIMargin(part);
284 m_ContentRect.Deflate(pUIMargin.left, pUIMargin.top, pUIMargin.width,
285 pUIMargin.height);
286
287 if (!IsDropDownStyle() || !m_pEdit)
288 return;
289
290 CFX_RectF rtEdit(m_ContentRect.left, m_ContentRect.top,
291 m_ContentRect.width - fBtn, m_ContentRect.height);
292 m_pEdit->SetWidgetRect(rtEdit);
293
294 if (m_iCurSel >= 0) {
295 CFWL_ListBox::Item* hItem = m_pListBox->GetItem(this, m_iCurSel);
296 ScopedUpdateLock update_lock(m_pEdit);
297 m_pEdit->SetText(hItem ? hItem->GetText() : WideString());
298 }
299 m_pEdit->Update();
300 }
301
ResetEditAlignment()302 void CFWL_ComboBox::ResetEditAlignment() {
303 if (!m_pEdit)
304 return;
305
306 uint32_t dwAdd = 0;
307 switch (m_Properties.m_dwStyleExts & FWL_STYLEEXT_CMB_EditHAlignMask) {
308 case FWL_STYLEEXT_CMB_EditHCenter: {
309 dwAdd |= FWL_STYLEEXT_EDT_HCenter;
310 break;
311 }
312 default: {
313 dwAdd |= FWL_STYLEEXT_EDT_HNear;
314 break;
315 }
316 }
317 switch (m_Properties.m_dwStyleExts & FWL_STYLEEXT_CMB_EditVAlignMask) {
318 case FWL_STYLEEXT_CMB_EditVCenter: {
319 dwAdd |= FWL_STYLEEXT_EDT_VCenter;
320 break;
321 }
322 case FWL_STYLEEXT_CMB_EditVFar: {
323 dwAdd |= FWL_STYLEEXT_EDT_VFar;
324 break;
325 }
326 default: {
327 dwAdd |= FWL_STYLEEXT_EDT_VNear;
328 break;
329 }
330 }
331 if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_CMB_EditJustified)
332 dwAdd |= FWL_STYLEEXT_EDT_Justified;
333
334 m_pEdit->ModifyStyleExts(dwAdd, FWL_STYLEEXT_EDT_HAlignMask |
335 FWL_STYLEEXT_EDT_HAlignModeMask |
336 FWL_STYLEEXT_EDT_VAlignMask);
337 }
338
ResetListItemAlignment()339 void CFWL_ComboBox::ResetListItemAlignment() {
340 if (!m_pListBox)
341 return;
342
343 uint32_t dwAdd = 0;
344 switch (m_Properties.m_dwStyleExts & FWL_STYLEEXT_CMB_ListItemAlignMask) {
345 case FWL_STYLEEXT_CMB_ListItemCenterAlign: {
346 dwAdd |= FWL_STYLEEXT_LTB_CenterAlign;
347 break;
348 }
349 default: {
350 dwAdd |= FWL_STYLEEXT_LTB_LeftAlign;
351 break;
352 }
353 }
354 m_pListBox->ModifyStyleExts(dwAdd, FWL_STYLEEXT_CMB_ListItemAlignMask);
355 }
356
ProcessSelChanged(bool bLButtonUp)357 void CFWL_ComboBox::ProcessSelChanged(bool bLButtonUp) {
358 m_iCurSel = m_pListBox->GetItemIndex(this, m_pListBox->GetSelItem(0));
359 if (!IsDropDownStyle()) {
360 RepaintRect(m_ClientRect);
361 return;
362 }
363 CFWL_ListBox::Item* hItem = m_pListBox->GetItem(this, m_iCurSel);
364 if (!hItem)
365 return;
366
367 if (m_pEdit) {
368 m_pEdit->SetText(hItem->GetText());
369 m_pEdit->Update();
370 m_pEdit->SetSelected();
371 }
372 CFWL_EventSelectChanged ev(this, bLButtonUp);
373 DispatchEvent(&ev);
374 }
375
OnProcessMessage(CFWL_Message * pMessage)376 void CFWL_ComboBox::OnProcessMessage(CFWL_Message* pMessage) {
377 bool backDefault = true;
378 switch (pMessage->GetType()) {
379 case CFWL_Message::Type::kSetFocus: {
380 backDefault = false;
381 OnFocusGained();
382 break;
383 }
384 case CFWL_Message::Type::kKillFocus: {
385 backDefault = false;
386 OnFocusLost();
387 break;
388 }
389 case CFWL_Message::Type::kMouse: {
390 backDefault = false;
391 CFWL_MessageMouse* pMsg = static_cast<CFWL_MessageMouse*>(pMessage);
392 switch (pMsg->m_dwCmd) {
393 case CFWL_MessageMouse::MouseCommand::kLeftButtonDown:
394 OnLButtonDown(pMsg);
395 break;
396 case CFWL_MessageMouse::MouseCommand::kLeftButtonUp:
397 OnLButtonUp(pMsg);
398 break;
399 default:
400 break;
401 }
402 break;
403 }
404 case CFWL_Message::Type::kKey: {
405 backDefault = false;
406 CFWL_MessageKey* pKey = static_cast<CFWL_MessageKey*>(pMessage);
407 if (IsDropListVisible() &&
408 pKey->m_dwCmd == CFWL_MessageKey::KeyCommand::kKeyDown) {
409 bool bListKey = pKey->m_dwKeyCodeOrChar == XFA_FWL_VKEY_Up ||
410 pKey->m_dwKeyCodeOrChar == XFA_FWL_VKEY_Down ||
411 pKey->m_dwKeyCodeOrChar == XFA_FWL_VKEY_Return ||
412 pKey->m_dwKeyCodeOrChar == XFA_FWL_VKEY_Escape;
413 if (bListKey) {
414 m_pListBox->GetDelegate()->OnProcessMessage(pMessage);
415 break;
416 }
417 }
418 OnKey(pKey);
419 break;
420 }
421 default:
422 break;
423 }
424 // Dst target could be |this|, continue only if not destroyed by above.
425 if (backDefault && pMessage->GetDstTarget())
426 CFWL_Widget::OnProcessMessage(pMessage);
427 }
428
OnProcessEvent(CFWL_Event * pEvent)429 void CFWL_ComboBox::OnProcessEvent(CFWL_Event* pEvent) {
430 CFWL_Event::Type type = pEvent->GetType();
431 if (type == CFWL_Event::Type::Scroll) {
432 CFWL_EventScroll* pScrollEvent = static_cast<CFWL_EventScroll*>(pEvent);
433 CFWL_EventScroll pScrollEv(this, pScrollEvent->GetScrollCode(),
434 pScrollEvent->GetPos());
435 DispatchEvent(&pScrollEv);
436 } else if (type == CFWL_Event::Type::TextWillChange) {
437 CFWL_Event pTemp(CFWL_Event::Type::EditChanged, this);
438 DispatchEvent(&pTemp);
439 }
440 }
441
OnDrawWidget(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & matrix)442 void CFWL_ComboBox::OnDrawWidget(CFGAS_GEGraphics* pGraphics,
443 const CFX_Matrix& matrix) {
444 DrawWidget(pGraphics, matrix);
445 }
446
OnLButtonUp(CFWL_MessageMouse * pMsg)447 void CFWL_ComboBox::OnLButtonUp(CFWL_MessageMouse* pMsg) {
448 if (m_BtnRect.Contains(pMsg->m_pos))
449 m_iBtnState = CFWL_PartState::kHovered;
450 else
451 m_iBtnState = CFWL_PartState::kNormal;
452
453 RepaintRect(m_BtnRect);
454 }
455
OnLButtonDown(CFWL_MessageMouse * pMsg)456 void CFWL_ComboBox::OnLButtonDown(CFWL_MessageMouse* pMsg) {
457 if (IsDropListVisible()) {
458 if (m_BtnRect.Contains(pMsg->m_pos))
459 HideDropDownList();
460 return;
461 }
462 if (!m_ClientRect.Contains(pMsg->m_pos))
463 return;
464
465 if (m_pEdit)
466 MatchEditText();
467 ShowDropDownList();
468 }
469
OnFocusGained()470 void CFWL_ComboBox::OnFocusGained() {
471 m_Properties.m_dwStates |= FWL_STATE_WGT_Focused;
472 if ((m_pEdit->GetStates() & FWL_STATE_WGT_Focused) == 0) {
473 CFWL_MessageSetFocus msg(m_pEdit);
474 m_pEdit->GetDelegate()->OnProcessMessage(&msg);
475 }
476 }
477
OnFocusLost()478 void CFWL_ComboBox::OnFocusLost() {
479 m_Properties.m_dwStates &= ~FWL_STATE_WGT_Focused;
480 HideDropDownList();
481 CFWL_MessageKillFocus msg(nullptr);
482 m_pEdit->GetDelegate()->OnProcessMessage(&msg);
483 }
484
OnKey(CFWL_MessageKey * pMsg)485 void CFWL_ComboBox::OnKey(CFWL_MessageKey* pMsg) {
486 uint32_t dwKeyCode = pMsg->m_dwKeyCodeOrChar;
487 const bool bUp = dwKeyCode == XFA_FWL_VKEY_Up;
488 const bool bDown = dwKeyCode == XFA_FWL_VKEY_Down;
489 if (bUp || bDown) {
490 CFWL_ComboList* pComboList = m_pListBox;
491 int32_t iCount = pComboList->CountItems(nullptr);
492 if (iCount < 1)
493 return;
494
495 bool bMatchEqual = false;
496 int32_t iCurSel = m_iCurSel;
497 if (m_pEdit) {
498 WideString wsText = m_pEdit->GetText();
499 iCurSel = pComboList->MatchItem(wsText.AsStringView());
500 if (iCurSel >= 0) {
501 CFWL_ListBox::Item* item = m_pListBox->GetSelItem(iCurSel);
502 bMatchEqual = wsText == (item ? item->GetText() : WideString());
503 }
504 }
505 if (iCurSel < 0) {
506 iCurSel = 0;
507 } else if (bMatchEqual) {
508 if ((bUp && iCurSel == 0) || (bDown && iCurSel == iCount - 1))
509 return;
510 if (bUp)
511 iCurSel--;
512 else
513 iCurSel++;
514 }
515 m_iCurSel = iCurSel;
516 SyncEditText(m_iCurSel);
517 return;
518 }
519 if (m_pEdit)
520 m_pEdit->GetDelegate()->OnProcessMessage(pMsg);
521 }
522
GetPopupPos(float fMinHeight,float fMaxHeight,const CFX_RectF & rtAnchor,CFX_RectF * pPopupRect)523 void CFWL_ComboBox::GetPopupPos(float fMinHeight,
524 float fMaxHeight,
525 const CFX_RectF& rtAnchor,
526 CFX_RectF* pPopupRect) {
527 GetWidgetMgr()->GetAdapterPopupPos(this, fMinHeight, fMaxHeight, rtAnchor,
528 pPopupRect);
529 }
530