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 "fpdfsdk/pwl/cpwl_combo_box.h"
8 
9 #include <algorithm>
10 #include <utility>
11 
12 #include "constants/ascii.h"
13 #include "fpdfsdk/pwl/cpwl_cbbutton.h"
14 #include "fpdfsdk/pwl/cpwl_cblistbox.h"
15 #include "fpdfsdk/pwl/cpwl_edit.h"
16 #include "fpdfsdk/pwl/ipwl_fillernotify.h"
17 #include "public/fpdf_fwlevent.h"
18 
19 namespace {
20 
21 constexpr float kComboBoxDefaultFontSize = 12.0f;
22 constexpr int kDefaultButtonWidth = 13;
23 
24 }  // namespace
25 
CPWL_ComboBox(const CreateParams & cp,std::unique_ptr<IPWL_FillerNotify::PerWindowData> pAttachedData)26 CPWL_ComboBox::CPWL_ComboBox(
27     const CreateParams& cp,
28     std::unique_ptr<IPWL_FillerNotify::PerWindowData> pAttachedData)
29     : CPWL_Wnd(cp, std::move(pAttachedData)) {
30   GetCreationParams()->dwFlags &= ~PWS_VSCROLL;
31 }
32 
33 CPWL_ComboBox::~CPWL_ComboBox() = default;
34 
OnDestroy()35 void CPWL_ComboBox::OnDestroy() {
36   // Until cleanup takes place in the virtual destructor for CPWL_Wnd
37   // subclasses, implement the virtual OnDestroy method that does the
38   // cleanup first, then invokes the superclass OnDestroy ... gee,
39   // like a dtor would.
40   m_pList.ExtractAsDangling();
41   m_pButton.ExtractAsDangling();
42   m_pEdit.ExtractAsDangling();
43   CPWL_Wnd::OnDestroy();
44 }
45 
SetFocus()46 void CPWL_ComboBox::SetFocus() {
47   if (m_pEdit)
48     m_pEdit->SetFocus();
49 }
50 
KillFocus()51 void CPWL_ComboBox::KillFocus() {
52   if (!SetPopup(false))
53     return;
54 
55   CPWL_Wnd::KillFocus();
56 }
57 
GetSelectedText()58 WideString CPWL_ComboBox::GetSelectedText() {
59   if (m_pEdit)
60     return m_pEdit->GetSelectedText();
61 
62   return WideString();
63 }
64 
ReplaceAndKeepSelection(const WideString & text)65 void CPWL_ComboBox::ReplaceAndKeepSelection(const WideString& text) {
66   if (m_pEdit)
67     m_pEdit->ReplaceAndKeepSelection(text);
68 }
69 
ReplaceSelection(const WideString & text)70 void CPWL_ComboBox::ReplaceSelection(const WideString& text) {
71   if (m_pEdit)
72     m_pEdit->ReplaceSelection(text);
73 }
74 
SelectAllText()75 bool CPWL_ComboBox::SelectAllText() {
76   return m_pEdit && m_pEdit->SelectAllText();
77 }
78 
CanUndo()79 bool CPWL_ComboBox::CanUndo() {
80   return m_pEdit && m_pEdit->CanUndo();
81 }
82 
CanRedo()83 bool CPWL_ComboBox::CanRedo() {
84   return m_pEdit && m_pEdit->CanRedo();
85 }
86 
Undo()87 bool CPWL_ComboBox::Undo() {
88   return m_pEdit && m_pEdit->Undo();
89 }
90 
Redo()91 bool CPWL_ComboBox::Redo() {
92   return m_pEdit && m_pEdit->Redo();
93 }
94 
GetText()95 WideString CPWL_ComboBox::GetText() {
96   return m_pEdit ? m_pEdit->GetText() : WideString();
97 }
98 
SetText(const WideString & text)99 void CPWL_ComboBox::SetText(const WideString& text) {
100   if (m_pEdit)
101     m_pEdit->SetText(text);
102 }
103 
AddString(const WideString & str)104 void CPWL_ComboBox::AddString(const WideString& str) {
105   if (m_pList)
106     m_pList->AddString(str);
107 }
108 
GetSelect() const109 int32_t CPWL_ComboBox::GetSelect() const {
110   return m_nSelectItem;
111 }
112 
SetSelect(int32_t nItemIndex)113 void CPWL_ComboBox::SetSelect(int32_t nItemIndex) {
114   if (m_pList)
115     m_pList->Select(nItemIndex);
116 
117   m_pEdit->SetText(m_pList->GetText());
118   m_nSelectItem = nItemIndex;
119 }
120 
SetEditSelection(int32_t nStartChar,int32_t nEndChar)121 void CPWL_ComboBox::SetEditSelection(int32_t nStartChar, int32_t nEndChar) {
122   if (m_pEdit)
123     m_pEdit->SetSelection(nStartChar, nEndChar);
124 }
125 
ClearSelection()126 void CPWL_ComboBox::ClearSelection() {
127   if (m_pEdit)
128     m_pEdit->ClearSelection();
129 }
130 
CreateChildWnd(const CreateParams & cp)131 void CPWL_ComboBox::CreateChildWnd(const CreateParams& cp) {
132   CreateEdit(cp);
133   CreateButton(cp);
134   CreateListBox(cp);
135 }
136 
CreateEdit(const CreateParams & cp)137 void CPWL_ComboBox::CreateEdit(const CreateParams& cp) {
138   if (m_pEdit)
139     return;
140 
141   CreateParams ecp = cp;
142   ecp.dwFlags =
143       PWS_VISIBLE | PWS_BORDER | PES_CENTER | PES_AUTOSCROLL | PES_UNDO;
144 
145   if (HasFlag(PWS_AUTOFONTSIZE))
146     ecp.dwFlags |= PWS_AUTOFONTSIZE;
147 
148   if (!HasFlag(PCBS_ALLOWCUSTOMTEXT))
149     ecp.dwFlags |= PWS_READONLY;
150 
151   ecp.rcRectWnd = CFX_FloatRect();
152   ecp.dwBorderWidth = 0;
153   ecp.nBorderStyle = BorderStyle::kSolid;
154 
155   auto pEdit = std::make_unique<CPWL_Edit>(ecp, CloneAttachedData());
156   m_pEdit = pEdit.get();
157   AddChild(std::move(pEdit));
158   m_pEdit->Realize();
159 }
160 
CreateButton(const CreateParams & cp)161 void CPWL_ComboBox::CreateButton(const CreateParams& cp) {
162   if (m_pButton)
163     return;
164 
165   CreateParams bcp = cp;
166   bcp.dwFlags = PWS_VISIBLE | PWS_BORDER | PWS_BACKGROUND;
167   bcp.sBackgroundColor = CFX_Color(CFX_Color::Type::kRGB, 220.0f / 255.0f,
168                                    220.0f / 255.0f, 220.0f / 255.0f);
169   bcp.sBorderColor = kDefaultBlackColor;
170   bcp.dwBorderWidth = 2;
171   bcp.nBorderStyle = BorderStyle::kBeveled;
172   bcp.eCursorType = IPWL_FillerNotify::CursorStyle::kArrow;
173 
174   auto pButton = std::make_unique<CPWL_CBButton>(bcp, CloneAttachedData());
175   m_pButton = pButton.get();
176   AddChild(std::move(pButton));
177   m_pButton->Realize();
178 }
179 
CreateListBox(const CreateParams & cp)180 void CPWL_ComboBox::CreateListBox(const CreateParams& cp) {
181   if (m_pList)
182     return;
183 
184   CreateParams lcp = cp;
185   lcp.dwFlags = PWS_BORDER | PWS_BACKGROUND | PLBS_HOVERSEL | PWS_VSCROLL;
186   lcp.nBorderStyle = BorderStyle::kSolid;
187   lcp.dwBorderWidth = 1;
188   lcp.eCursorType = IPWL_FillerNotify::CursorStyle::kArrow;
189   lcp.rcRectWnd = CFX_FloatRect();
190   lcp.fFontSize =
191       (cp.dwFlags & PWS_AUTOFONTSIZE) ? kComboBoxDefaultFontSize : cp.fFontSize;
192 
193   if (cp.sBorderColor.nColorType == CFX_Color::Type::kTransparent)
194     lcp.sBorderColor = kDefaultBlackColor;
195 
196   if (cp.sBackgroundColor.nColorType == CFX_Color::Type::kTransparent)
197     lcp.sBackgroundColor = kDefaultWhiteColor;
198 
199   auto pList = std::make_unique<CPWL_CBListBox>(lcp, CloneAttachedData());
200   m_pList = pList.get();
201   AddChild(std::move(pList));
202   m_pList->Realize();
203 }
204 
RePosChildWnd()205 bool CPWL_ComboBox::RePosChildWnd() {
206   ObservedPtr<CPWL_ComboBox> thisObserved(this);
207   const CFX_FloatRect rcClient = GetClientRect();
208   if (m_bPopup) {
209     const float fOldWindowHeight = m_rcOldWindow.Height();
210     const float fOldClientHeight = fOldWindowHeight - GetBorderWidth() * 2;
211 
212     CFX_FloatRect rcList = CPWL_Wnd::GetWindowRect();
213     CFX_FloatRect rcButton = rcClient;
214     rcButton.left =
215         std::max(rcButton.right - kDefaultButtonWidth, rcClient.left);
216     CFX_FloatRect rcEdit = rcClient;
217     rcEdit.right = std::max(rcButton.left - 1.0f, rcEdit.left);
218     if (m_bBottom) {
219       rcButton.bottom = rcButton.top - fOldClientHeight;
220       rcEdit.bottom = rcEdit.top - fOldClientHeight;
221       rcList.top -= fOldWindowHeight;
222     } else {
223       rcButton.top = rcButton.bottom + fOldClientHeight;
224       rcEdit.top = rcEdit.bottom + fOldClientHeight;
225       rcList.bottom += fOldWindowHeight;
226     }
227 
228     if (m_pButton) {
229       m_pButton->Move(rcButton, true, false);
230       if (!thisObserved)
231         return false;
232     }
233 
234     if (m_pEdit) {
235       m_pEdit->Move(rcEdit, true, false);
236       if (!thisObserved)
237         return false;
238     }
239 
240     if (m_pList) {
241       if (!m_pList->SetVisible(true) || !thisObserved)
242         return false;
243 
244       if (!m_pList->Move(rcList, true, false) || !thisObserved)
245         return false;
246 
247       m_pList->ScrollToListItem(m_nSelectItem);
248       if (!thisObserved)
249         return false;
250     }
251     return true;
252   }
253 
254   CFX_FloatRect rcButton = rcClient;
255   rcButton.left = std::max(rcButton.right - kDefaultButtonWidth, rcClient.left);
256 
257   if (m_pButton) {
258     m_pButton->Move(rcButton, true, false);
259     if (!thisObserved)
260       return false;
261   }
262 
263   CFX_FloatRect rcEdit = rcClient;
264   rcEdit.right = std::max(rcButton.left - 1.0f, rcEdit.left);
265 
266   if (m_pEdit) {
267     m_pEdit->Move(rcEdit, true, false);
268     if (!thisObserved)
269       return false;
270   }
271 
272   if (m_pList) {
273     m_pList->SetVisible(false);
274     if (!thisObserved)
275       return false;
276   }
277 
278   return true;
279 }
280 
SelectAll()281 void CPWL_ComboBox::SelectAll() {
282   if (m_pEdit && HasFlag(PCBS_ALLOWCUSTOMTEXT))
283     m_pEdit->SelectAllText();
284 }
285 
GetFocusRect() const286 CFX_FloatRect CPWL_ComboBox::GetFocusRect() const {
287   return CFX_FloatRect();
288 }
289 
SetPopup(bool bPopup)290 bool CPWL_ComboBox::SetPopup(bool bPopup) {
291   if (!m_pList)
292     return true;
293   if (bPopup == m_bPopup)
294     return true;
295   float fListHeight = m_pList->GetContentRect().Height();
296   if (!FXSYS_IsFloatBigger(fListHeight, 0.0f))
297     return true;
298 
299   if (!bPopup) {
300     m_bPopup = bPopup;
301     return Move(m_rcOldWindow, true, true);
302   }
303 
304   ObservedPtr<CPWL_ComboBox> thisObserved(this);
305   if (GetFillerNotify()->OnPopupPreOpen(GetAttachedData(), {}))
306     return !!thisObserved;
307   if (!thisObserved)
308     return false;
309 
310   float fBorderWidth = m_pList->GetBorderWidth() * 2;
311   float fPopupMin = 0.0f;
312   if (m_pList->GetCount() > 3)
313     fPopupMin = m_pList->GetFirstHeight() * 3 + fBorderWidth;
314   float fPopupMax = fListHeight + fBorderWidth;
315 
316   bool bBottom;
317   float fPopupRet;
318   GetFillerNotify()->QueryWherePopup(GetAttachedData(), fPopupMin, fPopupMax,
319                                      &bBottom, &fPopupRet);
320   if (!FXSYS_IsFloatBigger(fPopupRet, 0.0f))
321     return true;
322 
323   m_rcOldWindow = CPWL_Wnd::GetWindowRect();
324   m_bPopup = bPopup;
325   m_bBottom = bBottom;
326 
327   CFX_FloatRect rcWindow = m_rcOldWindow;
328   if (bBottom)
329     rcWindow.bottom -= fPopupRet;
330   else
331     rcWindow.top += fPopupRet;
332 
333   if (!Move(rcWindow, true, true))
334     return false;
335 
336   GetFillerNotify()->OnPopupPostOpen(GetAttachedData(), {});
337   return !!thisObserved;
338 }
339 
OnKeyDown(FWL_VKEYCODE nKeyCode,Mask<FWL_EVENTFLAG> nFlag)340 bool CPWL_ComboBox::OnKeyDown(FWL_VKEYCODE nKeyCode,
341                               Mask<FWL_EVENTFLAG> nFlag) {
342   if (!m_pList)
343     return false;
344   if (!m_pEdit)
345     return false;
346 
347   ObservedPtr<CPWL_Wnd> thisObserved(this);
348   m_nSelectItem = -1;
349 
350   switch (nKeyCode) {
351     case FWL_VKEY_Up:
352       if (m_pList->GetCurSel() > 0) {
353         if (GetFillerNotify()->OnPopupPreOpen(GetAttachedData(), nFlag) ||
354             !thisObserved) {
355           return false;
356         }
357         if (GetFillerNotify()->OnPopupPostOpen(GetAttachedData(), nFlag) ||
358             !thisObserved) {
359           return false;
360         }
361         if (m_pList->IsMovementKey(nKeyCode)) {
362           if (m_pList->OnMovementKeyDown(nKeyCode, nFlag) || !thisObserved) {
363             return false;
364           }
365           SetSelectText();
366         }
367       }
368       return true;
369     case FWL_VKEY_Down:
370       if (m_pList->GetCurSel() < m_pList->GetCount() - 1) {
371         if (GetFillerNotify()->OnPopupPreOpen(GetAttachedData(), nFlag) ||
372             !thisObserved) {
373           return false;
374         }
375         if (GetFillerNotify()->OnPopupPostOpen(GetAttachedData(), nFlag) ||
376             !thisObserved) {
377           return false;
378         }
379         if (m_pList->IsMovementKey(nKeyCode)) {
380           if (m_pList->OnMovementKeyDown(nKeyCode, nFlag) || !thisObserved) {
381             return false;
382           }
383           SetSelectText();
384         }
385       }
386       return true;
387     default:
388       break;
389   }
390 
391   if (HasFlag(PCBS_ALLOWCUSTOMTEXT))
392     return m_pEdit->OnKeyDown(nKeyCode, nFlag);
393 
394   return false;
395 }
396 
OnChar(uint16_t nChar,Mask<FWL_EVENTFLAG> nFlag)397 bool CPWL_ComboBox::OnChar(uint16_t nChar, Mask<FWL_EVENTFLAG> nFlag) {
398   if (!m_pList)
399     return false;
400 
401   if (!m_pEdit)
402     return false;
403 
404   // In a combo box if the ENTER/SPACE key is pressed, show the combo box
405   // options.
406   switch (nChar) {
407     case pdfium::ascii::kReturn:
408       if (!SetPopup(!IsPopup())) {
409         return false;
410       }
411       SetSelectText();
412       return true;
413     case pdfium::ascii::kSpace:
414       // Show the combo box options with space only if the combo box is not
415       // editable
416       if (!HasFlag(PCBS_ALLOWCUSTOMTEXT)) {
417         if (!IsPopup()) {
418           if (!SetPopup(/*bPopUp=*/true)) {
419             return false;
420           }
421           SetSelectText();
422         }
423         return true;
424       }
425       break;
426     default:
427       break;
428   }
429 
430   m_nSelectItem = -1;
431   if (HasFlag(PCBS_ALLOWCUSTOMTEXT))
432     return m_pEdit->OnChar(nChar, nFlag);
433 
434   ObservedPtr<CPWL_Wnd> thisObserved(this);
435   if (GetFillerNotify()->OnPopupPreOpen(GetAttachedData(), nFlag) ||
436       !thisObserved) {
437     return false;
438   }
439   if (GetFillerNotify()->OnPopupPostOpen(GetAttachedData(), nFlag) ||
440       !thisObserved) {
441     return false;
442   }
443   if (!m_pList->IsChar(nChar, nFlag))
444     return false;
445   return m_pList->OnCharNotify(nChar, nFlag);
446 }
447 
NotifyLButtonDown(CPWL_Wnd * child,const CFX_PointF & pos)448 void CPWL_ComboBox::NotifyLButtonDown(CPWL_Wnd* child, const CFX_PointF& pos) {
449   if (child == m_pButton) {
450     (void)SetPopup(!m_bPopup);
451     // Note, |this| may no longer be viable at this point. If more work needs to
452     // be done, check the return value of SetPopup().
453   }
454 }
455 
NotifyLButtonUp(CPWL_Wnd * child,const CFX_PointF & pos)456 void CPWL_ComboBox::NotifyLButtonUp(CPWL_Wnd* child, const CFX_PointF& pos) {
457   if (!m_pEdit || !m_pList || child != m_pList)
458     return;
459 
460   SetSelectText();
461   SelectAllText();
462   m_pEdit->SetFocus();
463   (void)SetPopup(false);
464   // Note, |this| may no longer be viable at this point. If more work needs to
465   // be done, check the return value of SetPopup().
466 }
467 
IsPopup() const468 bool CPWL_ComboBox::IsPopup() const {
469   return m_bPopup;
470 }
471 
SetSelectText()472 void CPWL_ComboBox::SetSelectText() {
473   m_pEdit->SelectAllText();
474   m_pEdit->ReplaceSelection(m_pList->GetText());
475   m_pEdit->SelectAllText();
476   m_nSelectItem = m_pList->GetCurSel();
477 }
478