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 "xfa/fxfa/cxfa_ffcombobox.h"
8 
9 #include <utility>
10 #include <vector>
11 
12 #include "third_party/base/ptr_util.h"
13 #include "xfa/fwl/cfwl_combobox.h"
14 #include "xfa/fwl/cfwl_eventselectchanged.h"
15 #include "xfa/fwl/cfwl_notedriver.h"
16 #include "xfa/fxfa/cxfa_eventparam.h"
17 #include "xfa/fxfa/cxfa_ffdocview.h"
18 #include "xfa/fxfa/parser/cxfa_para.h"
19 
20 namespace {
21 
ToComboBox(CFWL_Widget * widget)22 CFWL_ComboBox* ToComboBox(CFWL_Widget* widget) {
23   return static_cast<CFWL_ComboBox*>(widget);
24 }
25 
ToComboBox(const CFWL_Widget * widget)26 const CFWL_ComboBox* ToComboBox(const CFWL_Widget* widget) {
27   return static_cast<const CFWL_ComboBox*>(widget);
28 }
29 
30 }  // namespace
31 
CXFA_FFComboBox(CXFA_Node * pNode)32 CXFA_FFComboBox::CXFA_FFComboBox(CXFA_Node* pNode) : CXFA_FFDropDown(pNode) {}
33 
34 CXFA_FFComboBox::~CXFA_FFComboBox() = default;
35 
AsComboBox()36 CXFA_FFComboBox* CXFA_FFComboBox::AsComboBox() {
37   return this;
38 }
39 
GetBBox(FocusOption focus)40 CFX_RectF CXFA_FFComboBox::GetBBox(FocusOption focus) {
41   if (focus == kDrawFocus)
42     return CFX_RectF();
43   return CXFA_FFWidget::GetBBox(kDoNotDrawFocus);
44 }
45 
PtInActiveRect(const CFX_PointF & point)46 bool CXFA_FFComboBox::PtInActiveRect(const CFX_PointF& point) {
47   auto* pComboBox = ToComboBox(GetNormalWidget());
48   return pComboBox && pComboBox->GetBBox().Contains(point);
49 }
50 
LoadWidget()51 bool CXFA_FFComboBox::LoadWidget() {
52   ASSERT(!IsLoaded());
53 
54   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
55   RetainPtr<CXFA_ContentLayoutItem> retain_layout(m_pLayoutItem.Get());
56 
57   auto pNew = pdfium::MakeUnique<CFWL_ComboBox>(GetFWLApp());
58   CFWL_ComboBox* pComboBox = pNew.get();
59   SetNormalWidget(std::move(pNew));
60   pComboBox->SetAdapterIface(this);
61 
62   CFWL_NoteDriver* pNoteDriver = pComboBox->GetOwnerApp()->GetNoteDriver();
63   pNoteDriver->RegisterEventTarget(pComboBox, pComboBox);
64   m_pOldDelegate = pComboBox->GetDelegate();
65   pComboBox->SetDelegate(this);
66 
67   {
68     CFWL_Widget::ScopedUpdateLock update_lock(pComboBox);
69     for (const auto& label : m_pNode->GetChoiceListItems(false))
70       pComboBox->AddString(label);
71 
72     std::vector<int32_t> iSelArray = m_pNode->GetSelectedItems();
73     if (iSelArray.empty())
74       pComboBox->SetEditText(m_pNode->GetValue(XFA_VALUEPICTURE_Raw));
75     else
76       pComboBox->SetCurSel(iSelArray.front());
77 
78     UpdateWidgetProperty();
79   }
80 
81   return CXFA_FFField::LoadWidget();
82 }
83 
UpdateWidgetProperty()84 void CXFA_FFComboBox::UpdateWidgetProperty() {
85   auto* pComboBox = ToComboBox(GetNormalWidget());
86   if (!pComboBox)
87     return;
88 
89   uint32_t dwExtendedStyle = 0;
90   uint32_t dwEditStyles = FWL_STYLEEXT_EDT_ReadOnly;
91   dwExtendedStyle |= UpdateUIProperty();
92   if (m_pNode->IsChoiceListAllowTextEntry()) {
93     dwEditStyles &= ~FWL_STYLEEXT_EDT_ReadOnly;
94     dwExtendedStyle |= FWL_STYLEEXT_CMB_DropDown;
95   }
96   if (!m_pNode->IsOpenAccess() || !GetDoc()->GetXFADoc()->IsInteractive()) {
97     dwEditStyles |= FWL_STYLEEXT_EDT_ReadOnly;
98     dwExtendedStyle |= FWL_STYLEEXT_CMB_ReadOnly;
99   }
100   dwExtendedStyle |= GetAlignment();
101   GetNormalWidget()->ModifyStylesEx(dwExtendedStyle, 0xFFFFFFFF);
102 
103   if (!m_pNode->IsHorizontalScrollPolicyOff())
104     dwEditStyles |= FWL_STYLEEXT_EDT_AutoHScroll;
105 
106   pComboBox->EditModifyStylesEx(dwEditStyles, 0xFFFFFFFF);
107 }
108 
OnRButtonUp(uint32_t dwFlags,const CFX_PointF & point)109 bool CXFA_FFComboBox::OnRButtonUp(uint32_t dwFlags, const CFX_PointF& point) {
110   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
111   RetainPtr<CXFA_ContentLayoutItem> retainer(m_pLayoutItem.Get());
112 
113   if (!CXFA_FFField::OnRButtonUp(dwFlags, point))
114     return false;
115 
116   GetDoc()->GetDocEnvironment()->PopupMenu(this, point);
117   return true;
118 }
119 
OnKillFocus(CXFA_FFWidget * pNewWidget)120 bool CXFA_FFComboBox::OnKillFocus(CXFA_FFWidget* pNewWidget) {
121   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
122   RetainPtr<CXFA_ContentLayoutItem> retainer(m_pLayoutItem.Get());
123 
124   ObservedPtr<CXFA_FFWidget> pNewWatched(pNewWidget);
125   if (!ProcessCommittedData())
126     UpdateFWLData();
127 
128   return pNewWatched && CXFA_FFField::OnKillFocus(pNewWatched.Get());
129 }
130 
OpenDropDownList()131 void CXFA_FFComboBox::OpenDropDownList() {
132   ToComboBox(GetNormalWidget())->OpenDropDownList(true);
133 }
134 
CommitData()135 bool CXFA_FFComboBox::CommitData() {
136   return m_pNode->SetValue(XFA_VALUEPICTURE_Raw, m_wsNewValue);
137 }
138 
IsDataChanged()139 bool CXFA_FFComboBox::IsDataChanged() {
140   WideString wsText = GetCurrentText();
141   if (m_pNode->GetValue(XFA_VALUEPICTURE_Raw) == wsText)
142     return false;
143 
144   m_wsNewValue = std::move(wsText);
145   return true;
146 }
147 
FWLEventSelChange(CXFA_EventParam * pParam)148 void CXFA_FFComboBox::FWLEventSelChange(CXFA_EventParam* pParam) {
149   pParam->m_eType = XFA_EVENT_Change;
150   pParam->m_pTarget = m_pNode.Get();
151   pParam->m_wsPrevText = ToComboBox(GetNormalWidget())->GetEditText();
152   m_pNode->ProcessEvent(GetDocView(), XFA_AttributeValue::Change, pParam);
153 }
154 
GetCurrentText() const155 WideString CXFA_FFComboBox::GetCurrentText() const {
156   auto* pFWLcombobox = ToComboBox(GetNormalWidget());
157   WideString wsText = pFWLcombobox->GetEditText();
158   int32_t iCursel = pFWLcombobox->GetCurSel();
159   if (iCursel >= 0) {
160     WideString wsSel = pFWLcombobox->GetTextByIndex(iCursel);
161     if (wsSel == wsText)
162       wsText = m_pNode->GetChoiceListItem(iCursel, true).value_or(L"");
163   }
164   return wsText;
165 }
166 
GetAlignment()167 uint32_t CXFA_FFComboBox::GetAlignment() {
168   CXFA_Para* para = m_pNode->GetParaIfExists();
169   if (!para)
170     return 0;
171 
172   uint32_t dwExtendedStyle = 0;
173   switch (para->GetHorizontalAlign()) {
174     case XFA_AttributeValue::Center:
175       dwExtendedStyle |=
176           FWL_STYLEEXT_CMB_EditHCenter | FWL_STYLEEXT_CMB_ListItemCenterAlign;
177       break;
178     case XFA_AttributeValue::Justify:
179       dwExtendedStyle |= FWL_STYLEEXT_CMB_EditJustified;
180       break;
181     case XFA_AttributeValue::JustifyAll:
182       break;
183     case XFA_AttributeValue::Radix:
184       break;
185     case XFA_AttributeValue::Right:
186       break;
187     default:
188       dwExtendedStyle |=
189           FWL_STYLEEXT_CMB_EditHNear | FWL_STYLEEXT_CMB_ListItemLeftAlign;
190       break;
191   }
192 
193   switch (para->GetVerticalAlign()) {
194     case XFA_AttributeValue::Middle:
195       dwExtendedStyle |= FWL_STYLEEXT_CMB_EditVCenter;
196       break;
197     case XFA_AttributeValue::Bottom:
198       dwExtendedStyle |= FWL_STYLEEXT_CMB_EditVFar;
199       break;
200     default:
201       dwExtendedStyle |= FWL_STYLEEXT_CMB_EditVNear;
202       break;
203   }
204   return dwExtendedStyle;
205 }
206 
UpdateFWLData()207 bool CXFA_FFComboBox::UpdateFWLData() {
208   auto* pComboBox = ToComboBox(GetNormalWidget());
209   if (!pComboBox)
210     return false;
211 
212   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
213   RetainPtr<CXFA_ContentLayoutItem> retainer(m_pLayoutItem.Get());
214   std::vector<int32_t> iSelArray = m_pNode->GetSelectedItems();
215   if (!iSelArray.empty()) {
216     pComboBox->SetCurSel(iSelArray.front());
217   } else {
218     pComboBox->SetCurSel(-1);
219     pComboBox->SetEditText(m_pNode->GetValue(XFA_VALUEPICTURE_Raw));
220   }
221   pComboBox->Update();
222   return true;
223 }
224 
CanUndo()225 bool CXFA_FFComboBox::CanUndo() {
226   return m_pNode->IsChoiceListAllowTextEntry() &&
227          ToComboBox(GetNormalWidget())->EditCanUndo();
228 }
229 
CanRedo()230 bool CXFA_FFComboBox::CanRedo() {
231   return m_pNode->IsChoiceListAllowTextEntry() &&
232          ToComboBox(GetNormalWidget())->EditCanRedo();
233 }
234 
CanCopy()235 bool CXFA_FFComboBox::CanCopy() {
236   return ToComboBox(GetNormalWidget())->EditCanCopy();
237 }
238 
CanCut()239 bool CXFA_FFComboBox::CanCut() {
240   return m_pNode->IsOpenAccess() && m_pNode->IsChoiceListAllowTextEntry() &&
241          ToComboBox(GetNormalWidget())->EditCanCut();
242 }
243 
CanPaste()244 bool CXFA_FFComboBox::CanPaste() {
245   return m_pNode->IsChoiceListAllowTextEntry() && m_pNode->IsOpenAccess();
246 }
247 
CanSelectAll()248 bool CXFA_FFComboBox::CanSelectAll() {
249   return ToComboBox(GetNormalWidget())->EditCanSelectAll();
250 }
251 
Undo()252 bool CXFA_FFComboBox::Undo() {
253   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
254   RetainPtr<CXFA_ContentLayoutItem> retain_layout(m_pLayoutItem.Get());
255 
256   return m_pNode->IsChoiceListAllowTextEntry() &&
257          ToComboBox(GetNormalWidget())->EditUndo();
258 }
259 
Redo()260 bool CXFA_FFComboBox::Redo() {
261   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
262   RetainPtr<CXFA_ContentLayoutItem> retain_layout(m_pLayoutItem.Get());
263 
264   return m_pNode->IsChoiceListAllowTextEntry() &&
265          ToComboBox(GetNormalWidget())->EditRedo();
266 }
267 
Copy()268 Optional<WideString> CXFA_FFComboBox::Copy() {
269   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
270   RetainPtr<CXFA_ContentLayoutItem> retain_layout(m_pLayoutItem.Get());
271 
272   return ToComboBox(GetNormalWidget())->EditCopy();
273 }
274 
Cut()275 Optional<WideString> CXFA_FFComboBox::Cut() {
276   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
277   RetainPtr<CXFA_ContentLayoutItem> retain_layout(m_pLayoutItem.Get());
278 
279   if (!m_pNode->IsChoiceListAllowTextEntry())
280     return {};
281 
282   return ToComboBox(GetNormalWidget())->EditCut();
283 }
284 
Paste(const WideString & wsPaste)285 bool CXFA_FFComboBox::Paste(const WideString& wsPaste) {
286   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
287   RetainPtr<CXFA_ContentLayoutItem> retain_layout(m_pLayoutItem.Get());
288 
289   return m_pNode->IsChoiceListAllowTextEntry() &&
290          ToComboBox(GetNormalWidget())->EditPaste(wsPaste);
291 }
292 
SelectAll()293 void CXFA_FFComboBox::SelectAll() {
294   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
295   RetainPtr<CXFA_ContentLayoutItem> retain_layout(m_pLayoutItem.Get());
296 
297   ToComboBox(GetNormalWidget())->EditSelectAll();
298 }
299 
Delete()300 void CXFA_FFComboBox::Delete() {
301   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
302   RetainPtr<CXFA_ContentLayoutItem> retain_layout(m_pLayoutItem.Get());
303 
304   ToComboBox(GetNormalWidget())->EditDelete();
305 }
306 
DeSelect()307 void CXFA_FFComboBox::DeSelect() {
308   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
309   RetainPtr<CXFA_ContentLayoutItem> retain_layout(m_pLayoutItem.Get());
310 
311   ToComboBox(GetNormalWidget())->EditDeSelect();
312 }
313 
GetText()314 WideString CXFA_FFComboBox::GetText() {
315   return GetCurrentText();
316 }
317 
GetFormFieldType()318 FormFieldType CXFA_FFComboBox::GetFormFieldType() {
319   return FormFieldType::kXFA_ComboBox;
320 }
321 
SetItemState(int32_t nIndex,bool bSelected)322 void CXFA_FFComboBox::SetItemState(int32_t nIndex, bool bSelected) {
323   ToComboBox(GetNormalWidget())->SetCurSel(bSelected ? nIndex : -1);
324   GetNormalWidget()->Update();
325   InvalidateRect();
326 }
327 
InsertItem(const WideString & wsLabel,int32_t nIndex)328 void CXFA_FFComboBox::InsertItem(const WideString& wsLabel, int32_t nIndex) {
329   ToComboBox(GetNormalWidget())->AddString(wsLabel);
330   GetNormalWidget()->Update();
331   InvalidateRect();
332 }
333 
DeleteItem(int32_t nIndex)334 void CXFA_FFComboBox::DeleteItem(int32_t nIndex) {
335   if (nIndex < 0)
336     ToComboBox(GetNormalWidget())->RemoveAll();
337   else
338     ToComboBox(GetNormalWidget())->RemoveAt(nIndex);
339 
340   GetNormalWidget()->Update();
341   InvalidateRect();
342 }
343 
OnTextChanged(CFWL_Widget * pWidget,const WideString & wsChanged)344 void CXFA_FFComboBox::OnTextChanged(CFWL_Widget* pWidget,
345                                     const WideString& wsChanged) {
346   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
347   RetainPtr<CXFA_ContentLayoutItem> retainer(m_pLayoutItem.Get());
348 
349   CXFA_EventParam eParam;
350   eParam.m_wsPrevText = m_pNode->GetValue(XFA_VALUEPICTURE_Raw);
351   eParam.m_wsChange = wsChanged;
352   FWLEventSelChange(&eParam);
353 }
354 
OnSelectChanged(CFWL_Widget * pWidget,bool bLButtonUp)355 void CXFA_FFComboBox::OnSelectChanged(CFWL_Widget* pWidget, bool bLButtonUp) {
356   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
357   RetainPtr<CXFA_ContentLayoutItem> retainer(m_pLayoutItem.Get());
358 
359   CXFA_EventParam eParam;
360   eParam.m_wsPrevText = m_pNode->GetValue(XFA_VALUEPICTURE_Raw);
361   FWLEventSelChange(&eParam);
362   if (m_pNode->IsChoiceListCommitOnSelect() && bLButtonUp)
363     m_pDocView->SetFocusNode(nullptr);
364 }
365 
OnPreOpen(CFWL_Widget * pWidget)366 void CXFA_FFComboBox::OnPreOpen(CFWL_Widget* pWidget) {
367   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
368   RetainPtr<CXFA_ContentLayoutItem> retainer(m_pLayoutItem.Get());
369 
370   CXFA_EventParam eParam;
371   eParam.m_eType = XFA_EVENT_PreOpen;
372   eParam.m_pTarget = m_pNode.Get();
373   m_pNode->ProcessEvent(GetDocView(), XFA_AttributeValue::PreOpen, &eParam);
374 }
375 
OnPostOpen(CFWL_Widget * pWidget)376 void CXFA_FFComboBox::OnPostOpen(CFWL_Widget* pWidget) {
377   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
378   RetainPtr<CXFA_ContentLayoutItem> retainer(m_pLayoutItem.Get());
379 
380   CXFA_EventParam eParam;
381   eParam.m_eType = XFA_EVENT_PostOpen;
382   eParam.m_pTarget = m_pNode.Get();
383   m_pNode->ProcessEvent(GetDocView(), XFA_AttributeValue::PostOpen, &eParam);
384 }
385 
OnProcessMessage(CFWL_Message * pMessage)386 void CXFA_FFComboBox::OnProcessMessage(CFWL_Message* pMessage) {
387   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
388   RetainPtr<CXFA_ContentLayoutItem> retainer(m_pLayoutItem.Get());
389 
390   m_pOldDelegate->OnProcessMessage(pMessage);
391 }
392 
OnProcessEvent(CFWL_Event * pEvent)393 void CXFA_FFComboBox::OnProcessEvent(CFWL_Event* pEvent) {
394   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
395   RetainPtr<CXFA_ContentLayoutItem> retain_layout(m_pLayoutItem.Get());
396 
397   CXFA_FFField::OnProcessEvent(pEvent);
398   switch (pEvent->GetType()) {
399     case CFWL_Event::Type::SelectChanged: {
400       auto* postEvent = static_cast<CFWL_EventSelectChanged*>(pEvent);
401       OnSelectChanged(GetNormalWidget(), postEvent->bLButtonUp);
402       break;
403     }
404     case CFWL_Event::Type::EditChanged: {
405       WideString wsChanged;
406       OnTextChanged(GetNormalWidget(), wsChanged);
407       break;
408     }
409     case CFWL_Event::Type::PreDropDown: {
410       OnPreOpen(GetNormalWidget());
411       break;
412     }
413     case CFWL_Event::Type::PostDropDown: {
414       OnPostOpen(GetNormalWidget());
415       break;
416     }
417     default:
418       break;
419   }
420   m_pOldDelegate->OnProcessEvent(pEvent);
421 }
422 
OnDrawWidget(CXFA_Graphics * pGraphics,const CFX_Matrix & matrix)423 void CXFA_FFComboBox::OnDrawWidget(CXFA_Graphics* pGraphics,
424                                    const CFX_Matrix& matrix) {
425   // Prevents destruction of the CXFA_ContentLayoutItem that owns |this|.
426   RetainPtr<CXFA_ContentLayoutItem> retainer(m_pLayoutItem.Get());
427 
428   m_pOldDelegate->OnDrawWidget(pGraphics, matrix);
429 }
430