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