xref: /aosp_15_r20/external/pdfium/fpdfsdk/pwl/cpwl_combo_box.cpp (revision 3ac0a46f773bac49fa9476ec2b1cf3f8da5ec3a4)
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 
RepositionChildWnd()205 bool CPWL_ComboBox::RepositionChildWnd() {
206   ObservedPtr<CPWL_ComboBox> this_observed(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 (!this_observed) {
231         return false;
232       }
233     }
234 
235     if (m_pEdit) {
236       m_pEdit->Move(rcEdit, true, false);
237       if (!this_observed) {
238         return false;
239       }
240     }
241 
242     if (m_pList) {
243       if (!m_pList->SetVisible(true) || !this_observed) {
244         return false;
245       }
246 
247       if (!m_pList->Move(rcList, true, false) || !this_observed) {
248         return false;
249       }
250 
251       m_pList->ScrollToListItem(m_nSelectItem);
252       if (!this_observed) {
253         return false;
254       }
255     }
256     return true;
257   }
258 
259   CFX_FloatRect rcButton = rcClient;
260   rcButton.left = std::max(rcButton.right - kDefaultButtonWidth, rcClient.left);
261 
262   if (m_pButton) {
263     m_pButton->Move(rcButton, true, false);
264     if (!this_observed) {
265       return false;
266     }
267   }
268 
269   CFX_FloatRect rcEdit = rcClient;
270   rcEdit.right = std::max(rcButton.left - 1.0f, rcEdit.left);
271 
272   if (m_pEdit) {
273     m_pEdit->Move(rcEdit, true, false);
274     if (!this_observed) {
275       return false;
276     }
277   }
278 
279   if (m_pList) {
280     if (!m_pList->SetVisible(false)) {
281       m_pList = nullptr;  // Gone, dangling even.
282       return false;
283     }
284     if (!this_observed) {
285       return false;
286     }
287   }
288 
289   return true;
290 }
291 
SelectAll()292 void CPWL_ComboBox::SelectAll() {
293   if (m_pEdit && HasFlag(PCBS_ALLOWCUSTOMTEXT))
294     m_pEdit->SelectAllText();
295 }
296 
GetFocusRect() const297 CFX_FloatRect CPWL_ComboBox::GetFocusRect() const {
298   return CFX_FloatRect();
299 }
300 
SetPopup(bool bPopup)301 bool CPWL_ComboBox::SetPopup(bool bPopup) {
302   if (!m_pList)
303     return true;
304   if (bPopup == m_bPopup)
305     return true;
306   float fListHeight = m_pList->GetContentRect().Height();
307   if (!FXSYS_IsFloatBigger(fListHeight, 0.0f))
308     return true;
309 
310   if (!bPopup) {
311     m_bPopup = bPopup;
312     return Move(m_rcOldWindow, true, true);
313   }
314 
315   ObservedPtr<CPWL_ComboBox> this_observed(this);
316   if (GetFillerNotify()->OnPopupPreOpen(GetAttachedData(), {}))
317     return !!this_observed;
318   if (!this_observed) {
319     return false;
320   }
321 
322   float fBorderWidth = m_pList->GetBorderWidth() * 2;
323   float fPopupMin = 0.0f;
324   if (m_pList->GetCount() > 3)
325     fPopupMin = m_pList->GetFirstHeight() * 3 + fBorderWidth;
326   float fPopupMax = fListHeight + fBorderWidth;
327 
328   bool bBottom;
329   float fPopupRet;
330   GetFillerNotify()->QueryWherePopup(GetAttachedData(), fPopupMin, fPopupMax,
331                                      &bBottom, &fPopupRet);
332   if (!FXSYS_IsFloatBigger(fPopupRet, 0.0f))
333     return true;
334 
335   m_rcOldWindow = CPWL_Wnd::GetWindowRect();
336   m_bPopup = bPopup;
337   m_bBottom = bBottom;
338 
339   CFX_FloatRect rcWindow = m_rcOldWindow;
340   if (bBottom)
341     rcWindow.bottom -= fPopupRet;
342   else
343     rcWindow.top += fPopupRet;
344 
345   if (!Move(rcWindow, true, true))
346     return false;
347 
348   GetFillerNotify()->OnPopupPostOpen(GetAttachedData(), {});
349   return !!this_observed;
350 }
351 
OnKeyDown(FWL_VKEYCODE nKeyCode,Mask<FWL_EVENTFLAG> nFlag)352 bool CPWL_ComboBox::OnKeyDown(FWL_VKEYCODE nKeyCode,
353                               Mask<FWL_EVENTFLAG> nFlag) {
354   if (!m_pList)
355     return false;
356   if (!m_pEdit)
357     return false;
358 
359   ObservedPtr<CPWL_Wnd> this_observed(this);
360   m_nSelectItem = -1;
361 
362   switch (nKeyCode) {
363     case FWL_VKEY_Up:
364       if (m_pList->GetCurSel() > 0) {
365         if (GetFillerNotify()->OnPopupPreOpen(GetAttachedData(), nFlag) ||
366             !this_observed) {
367           return false;
368         }
369         if (GetFillerNotify()->OnPopupPostOpen(GetAttachedData(), nFlag) ||
370             !this_observed) {
371           return false;
372         }
373         if (m_pList->IsMovementKey(nKeyCode)) {
374           if (m_pList->OnMovementKeyDown(nKeyCode, nFlag) || !this_observed) {
375             return false;
376           }
377           SetSelectText();
378         }
379       }
380       return true;
381     case FWL_VKEY_Down:
382       if (m_pList->GetCurSel() < m_pList->GetCount() - 1) {
383         if (GetFillerNotify()->OnPopupPreOpen(GetAttachedData(), nFlag) ||
384             !this_observed) {
385           return false;
386         }
387         if (GetFillerNotify()->OnPopupPostOpen(GetAttachedData(), nFlag) ||
388             !this_observed) {
389           return false;
390         }
391         if (m_pList->IsMovementKey(nKeyCode)) {
392           if (m_pList->OnMovementKeyDown(nKeyCode, nFlag) || !this_observed) {
393             return false;
394           }
395           SetSelectText();
396         }
397       }
398       return true;
399     default:
400       break;
401   }
402 
403   if (HasFlag(PCBS_ALLOWCUSTOMTEXT))
404     return m_pEdit->OnKeyDown(nKeyCode, nFlag);
405 
406   return false;
407 }
408 
OnChar(uint16_t nChar,Mask<FWL_EVENTFLAG> nFlag)409 bool CPWL_ComboBox::OnChar(uint16_t nChar, Mask<FWL_EVENTFLAG> nFlag) {
410   if (!m_pList)
411     return false;
412 
413   if (!m_pEdit)
414     return false;
415 
416   // In a combo box if the ENTER/SPACE key is pressed, show the combo box
417   // options.
418   switch (nChar) {
419     case pdfium::ascii::kReturn:
420       if (!SetPopup(!IsPopup())) {
421         return false;
422       }
423       SetSelectText();
424       return true;
425     case pdfium::ascii::kSpace:
426       // Show the combo box options with space only if the combo box is not
427       // editable
428       if (!HasFlag(PCBS_ALLOWCUSTOMTEXT)) {
429         if (!IsPopup()) {
430           if (!SetPopup(/*bPopUp=*/true)) {
431             return false;
432           }
433           SetSelectText();
434         }
435         return true;
436       }
437       break;
438     default:
439       break;
440   }
441 
442   m_nSelectItem = -1;
443   if (HasFlag(PCBS_ALLOWCUSTOMTEXT))
444     return m_pEdit->OnChar(nChar, nFlag);
445 
446   ObservedPtr<CPWL_Wnd> this_observed(this);
447   if (GetFillerNotify()->OnPopupPreOpen(GetAttachedData(), nFlag) ||
448       !this_observed) {
449     return false;
450   }
451   if (GetFillerNotify()->OnPopupPostOpen(GetAttachedData(), nFlag) ||
452       !this_observed) {
453     return false;
454   }
455   if (!m_pList->IsChar(nChar, nFlag))
456     return false;
457   return m_pList->OnCharNotify(nChar, nFlag);
458 }
459 
NotifyLButtonDown(CPWL_Wnd * child,const CFX_PointF & pos)460 void CPWL_ComboBox::NotifyLButtonDown(CPWL_Wnd* child, const CFX_PointF& pos) {
461   if (child == m_pButton) {
462     (void)SetPopup(!m_bPopup);
463     // Note, |this| may no longer be viable at this point. If more work needs to
464     // be done, check the return value of SetPopup().
465   }
466 }
467 
NotifyLButtonUp(CPWL_Wnd * child,const CFX_PointF & pos)468 void CPWL_ComboBox::NotifyLButtonUp(CPWL_Wnd* child, const CFX_PointF& pos) {
469   if (!m_pEdit || !m_pList || child != m_pList)
470     return;
471 
472   SetSelectText();
473   SelectAllText();
474   m_pEdit->SetFocus();
475   (void)SetPopup(false);
476   // Note, |this| may no longer be viable at this point. If more work needs to
477   // be done, check the return value of SetPopup().
478 }
479 
IsPopup() const480 bool CPWL_ComboBox::IsPopup() const {
481   return m_bPopup;
482 }
483 
SetSelectText()484 void CPWL_ComboBox::SetSelectText() {
485   m_pEdit->SelectAllText();
486   m_pEdit->ReplaceSelection(m_pList->GetText());
487   m_pEdit->SelectAllText();
488   m_nSelectItem = m_pList->GetCurSel();
489 }
490