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 "fpdfsdk/pwl/cpwl_combo_box.h"
8
9 #include <algorithm>
10 #include <sstream>
11
12 #include "core/fxge/cfx_pathdata.h"
13 #include "core/fxge/cfx_renderdevice.h"
14 #include "fpdfsdk/pwl/cpwl_edit.h"
15 #include "fpdfsdk/pwl/cpwl_edit_ctrl.h"
16 #include "fpdfsdk/pwl/cpwl_list_box.h"
17 #include "fpdfsdk/pwl/cpwl_list_impl.h"
18 #include "fpdfsdk/pwl/cpwl_wnd.h"
19 #include "public/fpdf_fwlevent.h"
20
21 namespace {
22
23 constexpr float kComboBoxDefaultFontSize = 12.0f;
24 constexpr float kComboBoxTriangleHalfLength = 3.0f;
25 constexpr int kDefaultButtonWidth = 13;
26
27 } // namespace
28
OnLButtonUp(const CFX_PointF & point,uint32_t nFlag)29 bool CPWL_CBListBox::OnLButtonUp(const CFX_PointF& point, uint32_t nFlag) {
30 CPWL_Wnd::OnLButtonUp(point, nFlag);
31
32 if (!m_bMouseDown)
33 return true;
34
35 ReleaseCapture();
36 m_bMouseDown = false;
37
38 if (!ClientHitTest(point))
39 return true;
40 if (CPWL_Wnd* pParent = GetParentWindow())
41 pParent->NotifyLButtonUp(this, point);
42
43 return !OnNotifySelectionChanged(false, nFlag);
44 }
45
IsMovementKey(uint16_t nChar) const46 bool CPWL_CBListBox::IsMovementKey(uint16_t nChar) const {
47 switch (nChar) {
48 case FWL_VKEY_Up:
49 case FWL_VKEY_Down:
50 case FWL_VKEY_Home:
51 case FWL_VKEY_Left:
52 case FWL_VKEY_End:
53 case FWL_VKEY_Right:
54 return true;
55 default:
56 return false;
57 }
58 }
59
OnMovementKeyDown(uint16_t nChar,uint32_t nFlag)60 bool CPWL_CBListBox::OnMovementKeyDown(uint16_t nChar, uint32_t nFlag) {
61 ASSERT(IsMovementKey(nChar));
62
63 switch (nChar) {
64 case FWL_VKEY_Up:
65 m_pList->OnVK_UP(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
66 break;
67 case FWL_VKEY_Down:
68 m_pList->OnVK_DOWN(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
69 break;
70 case FWL_VKEY_Home:
71 m_pList->OnVK_HOME(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
72 break;
73 case FWL_VKEY_Left:
74 m_pList->OnVK_LEFT(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
75 break;
76 case FWL_VKEY_End:
77 m_pList->OnVK_END(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
78 break;
79 case FWL_VKEY_Right:
80 m_pList->OnVK_RIGHT(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
81 break;
82 }
83 return OnNotifySelectionChanged(true, nFlag);
84 }
85
IsChar(uint16_t nChar,uint32_t nFlag) const86 bool CPWL_CBListBox::IsChar(uint16_t nChar, uint32_t nFlag) const {
87 return m_pList->OnChar(nChar, IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
88 }
89
OnCharNotify(uint16_t nChar,uint32_t nFlag)90 bool CPWL_CBListBox::OnCharNotify(uint16_t nChar, uint32_t nFlag) {
91 if (CPWL_ComboBox* pComboBox = (CPWL_ComboBox*)GetParentWindow())
92 pComboBox->SetSelectText();
93
94 return OnNotifySelectionChanged(true, nFlag);
95 }
96
DrawThisAppearance(CFX_RenderDevice * pDevice,const CFX_Matrix & mtUser2Device)97 void CPWL_CBButton::DrawThisAppearance(CFX_RenderDevice* pDevice,
98 const CFX_Matrix& mtUser2Device) {
99 CPWL_Wnd::DrawThisAppearance(pDevice, mtUser2Device);
100
101 CFX_FloatRect rectWnd = CPWL_Wnd::GetWindowRect();
102
103 if (!IsVisible() || rectWnd.IsEmpty())
104 return;
105
106 CFX_PointF ptCenter = GetCenterPoint();
107
108 static constexpr float kComboBoxTriangleQuarterLength =
109 kComboBoxTriangleHalfLength * 0.5;
110 CFX_PointF pt1(ptCenter.x - kComboBoxTriangleHalfLength,
111 ptCenter.y + kComboBoxTriangleQuarterLength);
112 CFX_PointF pt2(ptCenter.x + kComboBoxTriangleHalfLength,
113 ptCenter.y + kComboBoxTriangleQuarterLength);
114 CFX_PointF pt3(ptCenter.x, ptCenter.y - kComboBoxTriangleQuarterLength);
115
116 if (IsFloatBigger(rectWnd.right - rectWnd.left,
117 kComboBoxTriangleHalfLength * 2) &&
118 IsFloatBigger(rectWnd.top - rectWnd.bottom,
119 kComboBoxTriangleHalfLength)) {
120 CFX_PathData path;
121 path.AppendPoint(pt1, FXPT_TYPE::MoveTo, false);
122 path.AppendPoint(pt2, FXPT_TYPE::LineTo, false);
123 path.AppendPoint(pt3, FXPT_TYPE::LineTo, false);
124 path.AppendPoint(pt1, FXPT_TYPE::LineTo, false);
125
126 pDevice->DrawPath(&path, &mtUser2Device, nullptr,
127 PWL_DEFAULT_BLACKCOLOR.ToFXColor(GetTransparency()), 0,
128 FXFILL_ALTERNATE);
129 }
130 }
131
OnLButtonDown(const CFX_PointF & point,uint32_t nFlag)132 bool CPWL_CBButton::OnLButtonDown(const CFX_PointF& point, uint32_t nFlag) {
133 CPWL_Wnd::OnLButtonDown(point, nFlag);
134
135 SetCapture();
136
137 if (CPWL_Wnd* pParent = GetParentWindow())
138 pParent->NotifyLButtonDown(this, point);
139
140 return true;
141 }
142
OnLButtonUp(const CFX_PointF & point,uint32_t nFlag)143 bool CPWL_CBButton::OnLButtonUp(const CFX_PointF& point, uint32_t nFlag) {
144 CPWL_Wnd::OnLButtonUp(point, nFlag);
145
146 ReleaseCapture();
147
148 return true;
149 }
150
CPWL_ComboBox()151 CPWL_ComboBox::CPWL_ComboBox() {}
152
~CPWL_ComboBox()153 CPWL_ComboBox::~CPWL_ComboBox() {}
154
GetClassName() const155 ByteString CPWL_ComboBox::GetClassName() const {
156 return "CPWL_ComboBox";
157 }
158
OnCreate(CreateParams * pParamsToAdjust)159 void CPWL_ComboBox::OnCreate(CreateParams* pParamsToAdjust) {
160 pParamsToAdjust->dwFlags &= ~PWS_HSCROLL;
161 pParamsToAdjust->dwFlags &= ~PWS_VSCROLL;
162 }
163
OnDestroy()164 void CPWL_ComboBox::OnDestroy() {
165 // Until cleanup takes place in the virtual destructor for CPWL_Wnd
166 // subclasses, implement the virtual OnDestroy method that does the
167 // cleanup first, then invokes the superclass OnDestroy ... gee,
168 // like a dtor would.
169 m_pList.Release();
170 m_pButton.Release();
171 m_pEdit.Release();
172 CPWL_Wnd::OnDestroy();
173 }
174
SetFocus()175 void CPWL_ComboBox::SetFocus() {
176 if (m_pEdit)
177 m_pEdit->SetFocus();
178 }
179
KillFocus()180 void CPWL_ComboBox::KillFocus() {
181 if (!SetPopup(false))
182 return;
183
184 CPWL_Wnd::KillFocus();
185 }
186
GetSelectedText()187 WideString CPWL_ComboBox::GetSelectedText() {
188 if (m_pEdit)
189 return m_pEdit->GetSelectedText();
190
191 return WideString();
192 }
193
ReplaceSelection(const WideString & text)194 void CPWL_ComboBox::ReplaceSelection(const WideString& text) {
195 if (m_pEdit)
196 m_pEdit->ReplaceSelection(text);
197 }
198
GetText() const199 WideString CPWL_ComboBox::GetText() const {
200 if (m_pEdit) {
201 return m_pEdit->GetText();
202 }
203 return WideString();
204 }
205
SetText(const WideString & text)206 void CPWL_ComboBox::SetText(const WideString& text) {
207 if (m_pEdit)
208 m_pEdit->SetText(text);
209 }
210
AddString(const WideString & str)211 void CPWL_ComboBox::AddString(const WideString& str) {
212 if (m_pList)
213 m_pList->AddString(str);
214 }
215
GetSelect() const216 int32_t CPWL_ComboBox::GetSelect() const {
217 return m_nSelectItem;
218 }
219
SetSelect(int32_t nItemIndex)220 void CPWL_ComboBox::SetSelect(int32_t nItemIndex) {
221 if (m_pList)
222 m_pList->Select(nItemIndex);
223
224 m_pEdit->SetText(m_pList->GetText());
225 m_nSelectItem = nItemIndex;
226 }
227
SetEditSelection(int32_t nStartChar,int32_t nEndChar)228 void CPWL_ComboBox::SetEditSelection(int32_t nStartChar, int32_t nEndChar) {
229 if (m_pEdit)
230 m_pEdit->SetSelection(nStartChar, nEndChar);
231 }
232
GetEditSelection(int32_t & nStartChar,int32_t & nEndChar) const233 void CPWL_ComboBox::GetEditSelection(int32_t& nStartChar,
234 int32_t& nEndChar) const {
235 nStartChar = -1;
236 nEndChar = -1;
237
238 if (m_pEdit)
239 m_pEdit->GetSelection(nStartChar, nEndChar);
240 }
241
ClearSelection()242 void CPWL_ComboBox::ClearSelection() {
243 if (m_pEdit)
244 m_pEdit->ClearSelection();
245 }
246
CreateChildWnd(const CreateParams & cp)247 void CPWL_ComboBox::CreateChildWnd(const CreateParams& cp) {
248 CreateEdit(cp);
249 CreateButton(cp);
250 CreateListBox(cp);
251 }
252
CreateEdit(const CreateParams & cp)253 void CPWL_ComboBox::CreateEdit(const CreateParams& cp) {
254 if (m_pEdit)
255 return;
256
257 m_pEdit = new CPWL_Edit();
258 m_pEdit->AttachFFLData(m_pFormFiller.Get());
259
260 CreateParams ecp = cp;
261 ecp.pParentWnd = this;
262 ecp.dwFlags = PWS_VISIBLE | PWS_CHILD | PWS_BORDER | PES_CENTER |
263 PES_AUTOSCROLL | PES_UNDO;
264
265 if (HasFlag(PWS_AUTOFONTSIZE))
266 ecp.dwFlags |= PWS_AUTOFONTSIZE;
267
268 if (!HasFlag(PCBS_ALLOWCUSTOMTEXT))
269 ecp.dwFlags |= PWS_READONLY;
270
271 ecp.rcRectWnd = CFX_FloatRect();
272 ecp.dwBorderWidth = 0;
273 ecp.nBorderStyle = BorderStyle::SOLID;
274 m_pEdit->Create(ecp);
275 }
276
CreateButton(const CreateParams & cp)277 void CPWL_ComboBox::CreateButton(const CreateParams& cp) {
278 if (m_pButton)
279 return;
280
281 m_pButton = new CPWL_CBButton;
282
283 CreateParams bcp = cp;
284 bcp.pParentWnd = this;
285 bcp.dwFlags = PWS_VISIBLE | PWS_CHILD | PWS_BORDER | PWS_BACKGROUND;
286 bcp.sBackgroundColor = CFX_Color(CFX_Color::kRGB, 220.0f / 255.0f,
287 220.0f / 255.0f, 220.0f / 255.0f);
288 bcp.sBorderColor = PWL_DEFAULT_BLACKCOLOR;
289 bcp.dwBorderWidth = 2;
290 bcp.nBorderStyle = BorderStyle::BEVELED;
291 bcp.eCursorType = FXCT_ARROW;
292 m_pButton->Create(bcp);
293 }
294
CreateListBox(const CreateParams & cp)295 void CPWL_ComboBox::CreateListBox(const CreateParams& cp) {
296 if (m_pList)
297 return;
298
299 m_pList = new CPWL_CBListBox();
300 m_pList->AttachFFLData(m_pFormFiller.Get());
301
302 CreateParams lcp = cp;
303 lcp.pParentWnd = this;
304 lcp.dwFlags =
305 PWS_CHILD | PWS_BORDER | PWS_BACKGROUND | PLBS_HOVERSEL | PWS_VSCROLL;
306 lcp.nBorderStyle = BorderStyle::SOLID;
307 lcp.dwBorderWidth = 1;
308 lcp.eCursorType = FXCT_ARROW;
309 lcp.rcRectWnd = CFX_FloatRect();
310
311 lcp.fFontSize =
312 (cp.dwFlags & PWS_AUTOFONTSIZE) ? kComboBoxDefaultFontSize : cp.fFontSize;
313
314 if (cp.sBorderColor.nColorType == CFX_Color::kTransparent)
315 lcp.sBorderColor = PWL_DEFAULT_BLACKCOLOR;
316
317 if (cp.sBackgroundColor.nColorType == CFX_Color::kTransparent)
318 lcp.sBackgroundColor = PWL_DEFAULT_WHITECOLOR;
319
320 m_pList->Create(lcp);
321 }
322
RePosChildWnd()323 bool CPWL_ComboBox::RePosChildWnd() {
324 ObservedPtr thisObserved(this);
325
326 const CFX_FloatRect rcClient = GetClientRect();
327 if (m_bPopup) {
328 const float fOldWindowHeight = m_rcOldWindow.Height();
329 const float fOldClientHeight = fOldWindowHeight - GetBorderWidth() * 2;
330
331 CFX_FloatRect rcList = CPWL_Wnd::GetWindowRect();
332 CFX_FloatRect rcButton = rcClient;
333 rcButton.left =
334 std::max(rcButton.right - kDefaultButtonWidth, rcClient.left);
335 CFX_FloatRect rcEdit = rcClient;
336 rcEdit.right = std::max(rcButton.left - 1.0f, rcEdit.left);
337 if (m_bBottom) {
338 rcButton.bottom = rcButton.top - fOldClientHeight;
339 rcEdit.bottom = rcEdit.top - fOldClientHeight;
340 rcList.top -= fOldWindowHeight;
341 } else {
342 rcButton.top = rcButton.bottom + fOldClientHeight;
343 rcEdit.top = rcEdit.bottom + fOldClientHeight;
344 rcList.bottom += fOldWindowHeight;
345 }
346
347 if (m_pButton) {
348 m_pButton->Move(rcButton, true, false);
349 if (!thisObserved)
350 return false;
351 }
352
353 if (m_pEdit) {
354 m_pEdit->Move(rcEdit, true, false);
355 if (!thisObserved)
356 return false;
357 }
358
359 if (m_pList) {
360 if (!m_pList->SetVisible(true) || !thisObserved)
361 return false;
362
363 if (!m_pList->Move(rcList, true, false) || !thisObserved)
364 return false;
365
366 m_pList->ScrollToListItem(m_nSelectItem);
367 if (!thisObserved)
368 return false;
369 }
370 return true;
371 }
372
373 CFX_FloatRect rcButton = rcClient;
374 rcButton.left = std::max(rcButton.right - kDefaultButtonWidth, rcClient.left);
375
376 if (m_pButton) {
377 m_pButton->Move(rcButton, true, false);
378 if (!thisObserved)
379 return false;
380 }
381
382 CFX_FloatRect rcEdit = rcClient;
383 rcEdit.right = std::max(rcButton.left - 1.0f, rcEdit.left);
384
385 if (m_pEdit) {
386 m_pEdit->Move(rcEdit, true, false);
387 if (!thisObserved)
388 return false;
389 }
390
391 if (m_pList) {
392 m_pList->SetVisible(false);
393 if (!thisObserved)
394 return false;
395 }
396
397 return true;
398 }
399
SelectAll()400 void CPWL_ComboBox::SelectAll() {
401 if (m_pEdit && HasFlag(PCBS_ALLOWCUSTOMTEXT))
402 m_pEdit->SelectAll();
403 }
404
GetFocusRect() const405 CFX_FloatRect CPWL_ComboBox::GetFocusRect() const {
406 return CFX_FloatRect();
407 }
408
SetPopup(bool bPopup)409 bool CPWL_ComboBox::SetPopup(bool bPopup) {
410 if (!m_pList)
411 return true;
412 if (bPopup == m_bPopup)
413 return true;
414 float fListHeight = m_pList->GetContentRect().Height();
415 if (!IsFloatBigger(fListHeight, 0.0f))
416 return true;
417
418 if (!bPopup) {
419 m_bPopup = bPopup;
420 return Move(m_rcOldWindow, true, true);
421 }
422
423 if (!m_pFillerNotify)
424 return true;
425
426 ObservedPtr thisObserved(this);
427
428 #ifdef PDF_ENABLE_XFA
429 if (m_pFillerNotify->OnPopupPreOpen(GetAttachedData(), 0))
430 return !!thisObserved;
431 if (!thisObserved)
432 return false;
433 #endif // PDF_ENABLE_XFA
434
435 float fBorderWidth = m_pList->GetBorderWidth() * 2;
436 float fPopupMin = 0.0f;
437 if (m_pList->GetCount() > 3)
438 fPopupMin = m_pList->GetFirstHeight() * 3 + fBorderWidth;
439 float fPopupMax = fListHeight + fBorderWidth;
440
441 bool bBottom;
442 float fPopupRet;
443 m_pFillerNotify->QueryWherePopup(GetAttachedData(), fPopupMin, fPopupMax,
444 &bBottom, &fPopupRet);
445 if (!IsFloatBigger(fPopupRet, 0.0f))
446 return true;
447
448 m_rcOldWindow = CPWL_Wnd::GetWindowRect();
449 m_bPopup = bPopup;
450 m_bBottom = bBottom;
451
452 CFX_FloatRect rcWindow = m_rcOldWindow;
453 if (bBottom)
454 rcWindow.bottom -= fPopupRet;
455 else
456 rcWindow.top += fPopupRet;
457
458 if (!Move(rcWindow, true, true))
459 return false;
460
461 #ifdef PDF_ENABLE_XFA
462 m_pFillerNotify->OnPopupPostOpen(GetAttachedData(), 0);
463 if (!thisObserved)
464 return false;
465 #endif // PDF_ENABLE_XFA
466
467 return !!thisObserved;
468 }
469
OnKeyDown(uint16_t nChar,uint32_t nFlag)470 bool CPWL_ComboBox::OnKeyDown(uint16_t nChar, uint32_t nFlag) {
471 if (!m_pList)
472 return false;
473 if (!m_pEdit)
474 return false;
475
476 m_nSelectItem = -1;
477
478 switch (nChar) {
479 case FWL_VKEY_Up:
480 if (m_pList->GetCurSel() > 0) {
481 #ifdef PDF_ENABLE_XFA
482 if (m_pFillerNotify) {
483 if (m_pFillerNotify->OnPopupPreOpen(GetAttachedData(), nFlag))
484 return false;
485 if (m_pFillerNotify->OnPopupPostOpen(GetAttachedData(), nFlag))
486 return false;
487 }
488 #endif // PDF_ENABLE_XFA
489 if (m_pList->IsMovementKey(nChar)) {
490 if (m_pList->OnMovementKeyDown(nChar, nFlag))
491 return false;
492 SetSelectText();
493 }
494 }
495 return true;
496 case FWL_VKEY_Down:
497 if (m_pList->GetCurSel() < m_pList->GetCount() - 1) {
498 #ifdef PDF_ENABLE_XFA
499 if (m_pFillerNotify) {
500 if (m_pFillerNotify->OnPopupPreOpen(GetAttachedData(), nFlag))
501 return false;
502 if (m_pFillerNotify->OnPopupPostOpen(GetAttachedData(), nFlag))
503 return false;
504 }
505 #endif // PDF_ENABLE_XFA
506 if (m_pList->IsMovementKey(nChar)) {
507 if (m_pList->OnMovementKeyDown(nChar, nFlag))
508 return false;
509 SetSelectText();
510 }
511 }
512 return true;
513 }
514
515 if (HasFlag(PCBS_ALLOWCUSTOMTEXT))
516 return m_pEdit->OnKeyDown(nChar, nFlag);
517
518 return false;
519 }
520
OnChar(uint16_t nChar,uint32_t nFlag)521 bool CPWL_ComboBox::OnChar(uint16_t nChar, uint32_t nFlag) {
522 if (!m_pList)
523 return false;
524
525 if (!m_pEdit)
526 return false;
527
528 m_nSelectItem = -1;
529 if (HasFlag(PCBS_ALLOWCUSTOMTEXT))
530 return m_pEdit->OnChar(nChar, nFlag);
531
532 #ifdef PDF_ENABLE_XFA
533 if (m_pFillerNotify) {
534 if (m_pFillerNotify->OnPopupPreOpen(GetAttachedData(), nFlag))
535 return false;
536 if (m_pFillerNotify->OnPopupPostOpen(GetAttachedData(), nFlag))
537 return false;
538 }
539 #endif // PDF_ENABLE_XFA
540 if (!m_pList->IsChar(nChar, nFlag))
541 return false;
542 return m_pList->OnCharNotify(nChar, nFlag);
543 }
544
NotifyLButtonDown(CPWL_Wnd * child,const CFX_PointF & pos)545 void CPWL_ComboBox::NotifyLButtonDown(CPWL_Wnd* child, const CFX_PointF& pos) {
546 if (child == m_pButton) {
547 SetPopup(!m_bPopup);
548 // Note, |this| may no longer be viable at this point. If more work needs to
549 // be done, check the return value of SetPopup().
550 }
551 }
552
NotifyLButtonUp(CPWL_Wnd * child,const CFX_PointF & pos)553 void CPWL_ComboBox::NotifyLButtonUp(CPWL_Wnd* child, const CFX_PointF& pos) {
554 if (!m_pEdit || !m_pList || child != m_pList)
555 return;
556
557 SetSelectText();
558 SelectAll();
559 m_pEdit->SetFocus();
560 SetPopup(false);
561 // Note, |this| may no longer be viable at this point. If more work needs to
562 // be done, check the return value of SetPopup().
563 }
564
IsPopup() const565 bool CPWL_ComboBox::IsPopup() const {
566 return m_bPopup;
567 }
568
SetSelectText()569 void CPWL_ComboBox::SetSelectText() {
570 m_pEdit->SelectAll();
571 m_pEdit->ReplaceSel(m_pList->GetText());
572 m_pEdit->SelectAll();
573 m_nSelectItem = m_pList->GetCurSel();
574 }
575
SetFillerNotify(IPWL_Filler_Notify * pNotify)576 void CPWL_ComboBox::SetFillerNotify(IPWL_Filler_Notify* pNotify) {
577 m_pFillerNotify = pNotify;
578
579 if (m_pEdit)
580 m_pEdit->SetFillerNotify(pNotify);
581
582 if (m_pList)
583 m_pList->SetFillerNotify(pNotify);
584 }
585