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