• 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 "xfa/fwl/cfwl_combobox.h"
8 
9 #include "v8/include/cppgc/visitor.h"
10 #include "xfa/fde/cfde_texteditengine.h"
11 #include "xfa/fde/cfde_textout.h"
12 #include "xfa/fwl/cfwl_app.h"
13 #include "xfa/fwl/cfwl_event.h"
14 #include "xfa/fwl/cfwl_eventselectchanged.h"
15 #include "xfa/fwl/cfwl_listbox.h"
16 #include "xfa/fwl/cfwl_messagekey.h"
17 #include "xfa/fwl/cfwl_messagekillfocus.h"
18 #include "xfa/fwl/cfwl_messagemouse.h"
19 #include "xfa/fwl/cfwl_messagesetfocus.h"
20 #include "xfa/fwl/cfwl_notedriver.h"
21 #include "xfa/fwl/cfwl_themebackground.h"
22 #include "xfa/fwl/cfwl_themepart.h"
23 #include "xfa/fwl/cfwl_themetext.h"
24 #include "xfa/fwl/cfwl_widgetmgr.h"
25 #include "xfa/fwl/fwl_widgetdef.h"
26 #include "xfa/fwl/ifwl_themeprovider.h"
27 
28 namespace pdfium {
29 
CFWL_ComboBox(CFWL_App * app)30 CFWL_ComboBox::CFWL_ComboBox(CFWL_App* app)
31     : CFWL_Widget(app, Properties(), nullptr),
32       m_pEdit(cppgc::MakeGarbageCollected<CFWL_ComboEdit>(
33           app->GetHeap()->GetAllocationHandle(),
34           app,
35           Properties(),
36           this)),
37       m_pListBox(cppgc::MakeGarbageCollected<CFWL_ComboList>(
38           app->GetHeap()->GetAllocationHandle(),
39           app,
40           Properties{FWL_STYLE_WGT_Border | FWL_STYLE_WGT_VScroll, 0,
41                      FWL_STATE_WGT_Invisible},
42           this)) {}
43 
44 CFWL_ComboBox::~CFWL_ComboBox() = default;
45 
Trace(cppgc::Visitor * visitor) const46 void CFWL_ComboBox::Trace(cppgc::Visitor* visitor) const {
47   CFWL_Widget::Trace(visitor);
48   visitor->Trace(m_pEdit);
49   visitor->Trace(m_pListBox);
50 }
51 
GetClassID() const52 FWL_Type CFWL_ComboBox::GetClassID() const {
53   return FWL_Type::ComboBox;
54 }
55 
AddString(const WideString & wsText)56 void CFWL_ComboBox::AddString(const WideString& wsText) {
57   m_pListBox->AddString(wsText);
58 }
59 
RemoveAt(int32_t iIndex)60 void CFWL_ComboBox::RemoveAt(int32_t iIndex) {
61   m_pListBox->RemoveAt(iIndex);
62 }
63 
RemoveAll()64 void CFWL_ComboBox::RemoveAll() {
65   m_pListBox->DeleteAll();
66 }
67 
ModifyStyleExts(uint32_t dwStyleExtsAdded,uint32_t dwStyleExtsRemoved)68 void CFWL_ComboBox::ModifyStyleExts(uint32_t dwStyleExtsAdded,
69                                     uint32_t dwStyleExtsRemoved) {
70   bool bAddDropDown = !!(dwStyleExtsAdded & FWL_STYLEEXT_CMB_DropDown);
71   bool bDelDropDown = !!(dwStyleExtsRemoved & FWL_STYLEEXT_CMB_DropDown);
72   dwStyleExtsRemoved &= ~FWL_STYLEEXT_CMB_DropDown;
73   m_Properties.m_dwStyleExts |= FWL_STYLEEXT_CMB_DropDown;
74   if (bAddDropDown)
75     m_pEdit->ModifyStyleExts(0, FWL_STYLEEXT_EDT_ReadOnly);
76   else if (bDelDropDown)
77     m_pEdit->ModifyStyleExts(FWL_STYLEEXT_EDT_ReadOnly, 0);
78 
79   CFWL_Widget::ModifyStyleExts(dwStyleExtsAdded, dwStyleExtsRemoved);
80 }
81 
Update()82 void CFWL_ComboBox::Update() {
83   if (IsLocked())
84     return;
85 
86   if (m_pEdit)
87     ResetEditAlignment();
88   Layout();
89 }
90 
HitTest(const CFX_PointF & point)91 FWL_WidgetHit CFWL_ComboBox::HitTest(const CFX_PointF& point) {
92   CFX_RectF rect(0, 0, m_WidgetRect.width - m_BtnRect.width,
93                  m_WidgetRect.height);
94   if (rect.Contains(point))
95     return FWL_WidgetHit::Edit;
96   if (m_BtnRect.Contains(point))
97     return FWL_WidgetHit::Client;
98   if (IsDropListVisible()) {
99     rect = m_pListBox->GetWidgetRect();
100     if (rect.Contains(point))
101       return FWL_WidgetHit::Client;
102   }
103   return FWL_WidgetHit::Unknown;
104 }
105 
DrawWidget(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & matrix)106 void CFWL_ComboBox::DrawWidget(CFGAS_GEGraphics* pGraphics,
107                                const CFX_Matrix& matrix) {
108   if (!m_BtnRect.IsEmpty(0.1f)) {
109     CFGAS_GEGraphics::StateRestorer restorer(pGraphics);
110     pGraphics->ConcatMatrix(matrix);
111     CFWL_ThemeBackground param(CFWL_ThemePart::Part::kDropDownButton, this,
112                                pGraphics);
113     param.m_dwStates = m_iBtnState;
114     param.m_PartRect = m_BtnRect;
115     GetThemeProvider()->DrawBackground(param);
116   }
117   if (m_pEdit) {
118     CFX_RectF rtEdit = m_pEdit->GetWidgetRect();
119     CFX_Matrix mt(1, 0, 0, 1, rtEdit.left, rtEdit.top);
120     mt.Concat(matrix);
121     m_pEdit->DrawWidget(pGraphics, mt);
122   }
123   if (m_pListBox && IsDropListVisible()) {
124     CFX_RectF rtList = m_pListBox->GetWidgetRect();
125     CFX_Matrix mt(1, 0, 0, 1, rtList.left, rtList.top);
126     mt.Concat(matrix);
127     m_pListBox->DrawWidget(pGraphics, mt);
128   }
129 }
130 
GetTextByIndex(int32_t iIndex) const131 WideString CFWL_ComboBox::GetTextByIndex(int32_t iIndex) const {
132   CFWL_ListBox::Item* pItem = m_pListBox->GetItem(m_pListBox, iIndex);
133   return pItem ? pItem->GetText() : WideString();
134 }
135 
SetCurSel(int32_t iSel)136 void CFWL_ComboBox::SetCurSel(int32_t iSel) {
137   int32_t iCount = m_pListBox->CountItems(nullptr);
138   bool bClearSel = iSel < 0 || iSel >= iCount;
139   if (IsDropDownStyle() && m_pEdit) {
140     if (bClearSel) {
141       m_pEdit->SetText(WideString());
142     } else {
143       CFWL_ListBox::Item* hItem = m_pListBox->GetItem(this, iSel);
144       m_pEdit->SetText(hItem ? hItem->GetText() : WideString());
145     }
146     m_pEdit->Update();
147   }
148   m_iCurSel = bClearSel ? -1 : iSel;
149 }
150 
SetStates(uint32_t dwStates)151 void CFWL_ComboBox::SetStates(uint32_t dwStates) {
152   if (IsDropDownStyle() && m_pEdit)
153     m_pEdit->SetStates(dwStates);
154   if (m_pListBox)
155     m_pListBox->SetStates(dwStates);
156   CFWL_Widget::SetStates(dwStates);
157 }
158 
RemoveStates(uint32_t dwStates)159 void CFWL_ComboBox::RemoveStates(uint32_t dwStates) {
160   if (IsDropDownStyle() && m_pEdit)
161     m_pEdit->RemoveStates(dwStates);
162   if (m_pListBox)
163     m_pListBox->RemoveStates(dwStates);
164   CFWL_Widget::RemoveStates(dwStates);
165 }
166 
SetEditText(const WideString & wsText)167 void CFWL_ComboBox::SetEditText(const WideString& wsText) {
168   if (!m_pEdit)
169     return;
170 
171   m_pEdit->SetText(wsText);
172   m_pEdit->Update();
173 }
174 
GetEditText() const175 WideString CFWL_ComboBox::GetEditText() const {
176   if (m_pEdit)
177     return m_pEdit->GetText();
178   if (!m_pListBox)
179     return WideString();
180 
181   CFWL_ListBox::Item* hItem = m_pListBox->GetItem(this, m_iCurSel);
182   return hItem ? hItem->GetText() : WideString();
183 }
184 
GetBBox() const185 CFX_RectF CFWL_ComboBox::GetBBox() const {
186   CFX_RectF rect = m_WidgetRect;
187   if (!m_pListBox || !IsDropListVisible())
188     return rect;
189 
190   CFX_RectF rtList = m_pListBox->GetWidgetRect();
191   rtList.Offset(rect.left, rect.top);
192   rect.Union(rtList);
193   return rect;
194 }
195 
EditModifyStyleExts(uint32_t dwStyleExtsAdded,uint32_t dwStyleExtsRemoved)196 void CFWL_ComboBox::EditModifyStyleExts(uint32_t dwStyleExtsAdded,
197                                         uint32_t dwStyleExtsRemoved) {
198   if (m_pEdit)
199     m_pEdit->ModifyStyleExts(dwStyleExtsAdded, dwStyleExtsRemoved);
200 }
201 
ShowDropDownList()202 void CFWL_ComboBox::ShowDropDownList() {
203   if (IsDropListVisible())
204     return;
205 
206   CFWL_Event preEvent(CFWL_Event::Type::PreDropDown, this);
207   DispatchEvent(&preEvent);
208   if (!preEvent.GetSrcTarget())
209     return;
210 
211   CFWL_ComboList* pComboList = m_pListBox;
212   int32_t iItems = pComboList->CountItems(nullptr);
213   if (iItems < 1)
214     return;
215 
216   ResetListItemAlignment();
217   pComboList->ChangeSelected(m_iCurSel);
218 
219   float fItemHeight = pComboList->CalcItemHeight();
220   float fBorder = GetCXBorderSize();
221   float fPopupMin = 0.0f;
222   if (iItems > 3)
223     fPopupMin = fItemHeight * 3 + fBorder * 2;
224 
225   float fPopupMax = fItemHeight * iItems + fBorder * 2;
226   CFX_RectF rtList(m_ClientRect.left, 0, m_WidgetRect.width, 0);
227   GetPopupPos(fPopupMin, fPopupMax, m_WidgetRect, &rtList);
228   m_pListBox->SetWidgetRect(rtList);
229   m_pListBox->Update();
230   m_pListBox->RemoveStates(FWL_STATE_WGT_Invisible);
231 
232   CFWL_Event postEvent(CFWL_Event::Type::PostDropDown, this);
233   DispatchEvent(&postEvent);
234   RepaintInflatedListBoxRect();
235 }
236 
HideDropDownList()237 void CFWL_ComboBox::HideDropDownList() {
238   if (!IsDropListVisible())
239     return;
240 
241   m_pListBox->SetStates(FWL_STATE_WGT_Invisible);
242   RepaintInflatedListBoxRect();
243 }
244 
RepaintInflatedListBoxRect()245 void CFWL_ComboBox::RepaintInflatedListBoxRect() {
246   CFX_RectF rect = m_pListBox->GetWidgetRect();
247   rect.Inflate(2, 2);
248   RepaintRect(rect);
249 }
250 
MatchEditText()251 void CFWL_ComboBox::MatchEditText() {
252   WideString wsText = m_pEdit->GetText();
253   int32_t iMatch = m_pListBox->MatchItem(wsText.AsStringView());
254   if (iMatch != m_iCurSel) {
255     m_pListBox->ChangeSelected(iMatch);
256     if (iMatch >= 0)
257       SyncEditText(iMatch);
258   } else if (iMatch >= 0) {
259     m_pEdit->SetSelected();
260   }
261   m_iCurSel = iMatch;
262 }
263 
SyncEditText(int32_t iListItem)264 void CFWL_ComboBox::SyncEditText(int32_t iListItem) {
265   CFWL_ListBox::Item* hItem = m_pListBox->GetItem(this, iListItem);
266   m_pEdit->SetText(hItem ? hItem->GetText() : WideString());
267   m_pEdit->Update();
268   m_pEdit->SetSelected();
269 }
270 
Layout()271 void CFWL_ComboBox::Layout() {
272   m_ClientRect = GetClientRect();
273   m_ContentRect = m_ClientRect;
274 
275   IFWL_ThemeProvider* theme = GetThemeProvider();
276   float borderWidth = 1;
277   float fBtn = theme->GetScrollBarWidth();
278   if (!(GetStyleExts() & FWL_STYLEEXT_CMB_ReadOnly)) {
279     m_BtnRect =
280         CFX_RectF(m_ClientRect.right() - fBtn, m_ClientRect.top + borderWidth,
281                   fBtn - borderWidth, m_ClientRect.height - 2 * borderWidth);
282   }
283 
284   CFWL_ThemePart part(CFWL_ThemePart::Part::kNone, this);
285   CFX_RectF pUIMargin = theme->GetUIMargin(part);
286   m_ContentRect.Deflate(pUIMargin.left, pUIMargin.top, pUIMargin.width,
287                         pUIMargin.height);
288 
289   if (!IsDropDownStyle() || !m_pEdit)
290     return;
291 
292   CFX_RectF rtEdit(m_ContentRect.left, m_ContentRect.top,
293                    m_ContentRect.width - fBtn, m_ContentRect.height);
294   m_pEdit->SetWidgetRect(rtEdit);
295 
296   if (m_iCurSel >= 0) {
297     CFWL_ListBox::Item* hItem = m_pListBox->GetItem(this, m_iCurSel);
298     ScopedUpdateLock update_lock(m_pEdit);
299     m_pEdit->SetText(hItem ? hItem->GetText() : WideString());
300   }
301   m_pEdit->Update();
302 }
303 
ResetEditAlignment()304 void CFWL_ComboBox::ResetEditAlignment() {
305   if (!m_pEdit)
306     return;
307 
308   uint32_t dwAdd = 0;
309   switch (m_Properties.m_dwStyleExts & FWL_STYLEEXT_CMB_EditHAlignMask) {
310     case FWL_STYLEEXT_CMB_EditHCenter: {
311       dwAdd |= FWL_STYLEEXT_EDT_HCenter;
312       break;
313     }
314     default: {
315       dwAdd |= FWL_STYLEEXT_EDT_HNear;
316       break;
317     }
318   }
319   switch (m_Properties.m_dwStyleExts & FWL_STYLEEXT_CMB_EditVAlignMask) {
320     case FWL_STYLEEXT_CMB_EditVCenter: {
321       dwAdd |= FWL_STYLEEXT_EDT_VCenter;
322       break;
323     }
324     case FWL_STYLEEXT_CMB_EditVFar: {
325       dwAdd |= FWL_STYLEEXT_EDT_VFar;
326       break;
327     }
328     default: {
329       dwAdd |= FWL_STYLEEXT_EDT_VNear;
330       break;
331     }
332   }
333   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_CMB_EditJustified)
334     dwAdd |= FWL_STYLEEXT_EDT_Justified;
335 
336   m_pEdit->ModifyStyleExts(dwAdd, FWL_STYLEEXT_EDT_HAlignMask |
337                                       FWL_STYLEEXT_EDT_HAlignModeMask |
338                                       FWL_STYLEEXT_EDT_VAlignMask);
339 }
340 
ResetListItemAlignment()341 void CFWL_ComboBox::ResetListItemAlignment() {
342   if (!m_pListBox)
343     return;
344 
345   uint32_t dwAdd = 0;
346   switch (m_Properties.m_dwStyleExts & FWL_STYLEEXT_CMB_ListItemAlignMask) {
347     case FWL_STYLEEXT_CMB_ListItemCenterAlign: {
348       dwAdd |= FWL_STYLEEXT_LTB_CenterAlign;
349       break;
350     }
351     default: {
352       dwAdd |= FWL_STYLEEXT_LTB_LeftAlign;
353       break;
354     }
355   }
356   m_pListBox->ModifyStyleExts(dwAdd, FWL_STYLEEXT_CMB_ListItemAlignMask);
357 }
358 
ProcessSelChanged(bool bLButtonUp)359 void CFWL_ComboBox::ProcessSelChanged(bool bLButtonUp) {
360   m_iCurSel = m_pListBox->GetItemIndex(this, m_pListBox->GetSelItem(0));
361   if (!IsDropDownStyle()) {
362     RepaintRect(m_ClientRect);
363     return;
364   }
365   CFWL_ListBox::Item* hItem = m_pListBox->GetItem(this, m_iCurSel);
366   if (!hItem)
367     return;
368 
369   if (m_pEdit) {
370     m_pEdit->SetText(hItem->GetText());
371     m_pEdit->Update();
372     m_pEdit->SetSelected();
373   }
374   CFWL_EventSelectChanged ev(this, bLButtonUp);
375   DispatchEvent(&ev);
376 }
377 
OnProcessMessage(CFWL_Message * pMessage)378 void CFWL_ComboBox::OnProcessMessage(CFWL_Message* pMessage) {
379   bool backDefault = true;
380   switch (pMessage->GetType()) {
381     case CFWL_Message::Type::kSetFocus: {
382       backDefault = false;
383       OnFocusGained();
384       break;
385     }
386     case CFWL_Message::Type::kKillFocus: {
387       backDefault = false;
388       OnFocusLost();
389       break;
390     }
391     case CFWL_Message::Type::kMouse: {
392       backDefault = false;
393       CFWL_MessageMouse* pMsg = static_cast<CFWL_MessageMouse*>(pMessage);
394       switch (pMsg->m_dwCmd) {
395         case CFWL_MessageMouse::MouseCommand::kLeftButtonDown:
396           OnLButtonDown(pMsg);
397           break;
398         case CFWL_MessageMouse::MouseCommand::kLeftButtonUp:
399           OnLButtonUp(pMsg);
400           break;
401         default:
402           break;
403       }
404       break;
405     }
406     case CFWL_Message::Type::kKey: {
407       backDefault = false;
408       CFWL_MessageKey* pKey = static_cast<CFWL_MessageKey*>(pMessage);
409       if (IsDropListVisible() &&
410           pKey->m_dwCmd == CFWL_MessageKey::KeyCommand::kKeyDown) {
411         bool bListKey = pKey->m_dwKeyCodeOrChar == XFA_FWL_VKEY_Up ||
412                         pKey->m_dwKeyCodeOrChar == XFA_FWL_VKEY_Down ||
413                         pKey->m_dwKeyCodeOrChar == XFA_FWL_VKEY_Return ||
414                         pKey->m_dwKeyCodeOrChar == XFA_FWL_VKEY_Escape;
415         if (bListKey) {
416           m_pListBox->GetDelegate()->OnProcessMessage(pMessage);
417           break;
418         }
419       }
420       OnKey(pKey);
421       break;
422     }
423     default:
424       break;
425   }
426   // Dst target could be |this|, continue only if not destroyed by above.
427   if (backDefault && pMessage->GetDstTarget())
428     CFWL_Widget::OnProcessMessage(pMessage);
429 }
430 
OnProcessEvent(CFWL_Event * pEvent)431 void CFWL_ComboBox::OnProcessEvent(CFWL_Event* pEvent) {
432   CFWL_Event::Type type = pEvent->GetType();
433   if (type == CFWL_Event::Type::Scroll) {
434     CFWL_EventScroll* pScrollEvent = static_cast<CFWL_EventScroll*>(pEvent);
435     CFWL_EventScroll pScrollEv(this, pScrollEvent->GetScrollCode(),
436                                pScrollEvent->GetPos());
437     DispatchEvent(&pScrollEv);
438   } else if (type == CFWL_Event::Type::TextWillChange) {
439     CFWL_Event pTemp(CFWL_Event::Type::EditChanged, this);
440     DispatchEvent(&pTemp);
441   }
442 }
443 
OnDrawWidget(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & matrix)444 void CFWL_ComboBox::OnDrawWidget(CFGAS_GEGraphics* pGraphics,
445                                  const CFX_Matrix& matrix) {
446   DrawWidget(pGraphics, matrix);
447 }
448 
OnLButtonUp(CFWL_MessageMouse * pMsg)449 void CFWL_ComboBox::OnLButtonUp(CFWL_MessageMouse* pMsg) {
450   if (m_BtnRect.Contains(pMsg->m_pos))
451     m_iBtnState = CFWL_PartState::kHovered;
452   else
453     m_iBtnState = CFWL_PartState::kNormal;
454 
455   RepaintRect(m_BtnRect);
456 }
457 
OnLButtonDown(CFWL_MessageMouse * pMsg)458 void CFWL_ComboBox::OnLButtonDown(CFWL_MessageMouse* pMsg) {
459   if (IsDropListVisible()) {
460     if (m_BtnRect.Contains(pMsg->m_pos))
461       HideDropDownList();
462     return;
463   }
464   if (!m_ClientRect.Contains(pMsg->m_pos))
465     return;
466 
467   if (m_pEdit)
468     MatchEditText();
469   ShowDropDownList();
470 }
471 
OnFocusGained()472 void CFWL_ComboBox::OnFocusGained() {
473   m_Properties.m_dwStates |= FWL_STATE_WGT_Focused;
474   if ((m_pEdit->GetStates() & FWL_STATE_WGT_Focused) == 0) {
475     CFWL_MessageSetFocus msg(m_pEdit);
476     m_pEdit->GetDelegate()->OnProcessMessage(&msg);
477   }
478 }
479 
OnFocusLost()480 void CFWL_ComboBox::OnFocusLost() {
481   m_Properties.m_dwStates &= ~FWL_STATE_WGT_Focused;
482   HideDropDownList();
483   CFWL_MessageKillFocus msg(nullptr);
484   m_pEdit->GetDelegate()->OnProcessMessage(&msg);
485 }
486 
OnKey(CFWL_MessageKey * pMsg)487 void CFWL_ComboBox::OnKey(CFWL_MessageKey* pMsg) {
488   uint32_t dwKeyCode = pMsg->m_dwKeyCodeOrChar;
489   const bool bUp = dwKeyCode == XFA_FWL_VKEY_Up;
490   const bool bDown = dwKeyCode == XFA_FWL_VKEY_Down;
491   if (bUp || bDown) {
492     CFWL_ComboList* pComboList = m_pListBox;
493     int32_t iCount = pComboList->CountItems(nullptr);
494     if (iCount < 1)
495       return;
496 
497     bool bMatchEqual = false;
498     int32_t iCurSel = m_iCurSel;
499     if (m_pEdit) {
500       WideString wsText = m_pEdit->GetText();
501       iCurSel = pComboList->MatchItem(wsText.AsStringView());
502       if (iCurSel >= 0) {
503         CFWL_ListBox::Item* item = m_pListBox->GetSelItem(iCurSel);
504         bMatchEqual = wsText == (item ? item->GetText() : WideString());
505       }
506     }
507     if (iCurSel < 0) {
508       iCurSel = 0;
509     } else if (bMatchEqual) {
510       if ((bUp && iCurSel == 0) || (bDown && iCurSel == iCount - 1))
511         return;
512       if (bUp)
513         iCurSel--;
514       else
515         iCurSel++;
516     }
517     m_iCurSel = iCurSel;
518     SyncEditText(m_iCurSel);
519     return;
520   }
521   if (m_pEdit)
522     m_pEdit->GetDelegate()->OnProcessMessage(pMsg);
523 }
524 
GetPopupPos(float fMinHeight,float fMaxHeight,const CFX_RectF & rtAnchor,CFX_RectF * pPopupRect)525 void CFWL_ComboBox::GetPopupPos(float fMinHeight,
526                                 float fMaxHeight,
527                                 const CFX_RectF& rtAnchor,
528                                 CFX_RectF* pPopupRect) {
529   GetWidgetMgr()->GetAdapterPopupPos(this, fMinHeight, fMaxHeight, rtAnchor,
530                                      pPopupRect);
531 }
532 
533 }  // namespace pdfium
534