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