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/fwl/cfwl_listbox.h"
8 
9 #include <algorithm>
10 #include <memory>
11 #include <utility>
12 
13 #include "third_party/base/ptr_util.h"
14 #include "third_party/base/stl_util.h"
15 #include "xfa/fde/cfde_textout.h"
16 #include "xfa/fwl/cfwl_app.h"
17 #include "xfa/fwl/cfwl_messagekey.h"
18 #include "xfa/fwl/cfwl_messagemouse.h"
19 #include "xfa/fwl/cfwl_messagemousewheel.h"
20 #include "xfa/fwl/cfwl_themebackground.h"
21 #include "xfa/fwl/cfwl_themepart.h"
22 #include "xfa/fwl/cfwl_themetext.h"
23 #include "xfa/fwl/fwl_widgetdef.h"
24 #include "xfa/fwl/ifwl_themeprovider.h"
25 
26 namespace {
27 
28 const int kItemTextMargin = 2;
29 
30 }  // namespace
31 
CFWL_ListBox(const CFWL_App * app,std::unique_ptr<CFWL_WidgetProperties> properties,CFWL_Widget * pOuter)32 CFWL_ListBox::CFWL_ListBox(const CFWL_App* app,
33                            std::unique_ptr<CFWL_WidgetProperties> properties,
34                            CFWL_Widget* pOuter)
35     : CFWL_Widget(app, std::move(properties), pOuter) {}
36 
~CFWL_ListBox()37 CFWL_ListBox::~CFWL_ListBox() {}
38 
GetClassID() const39 FWL_Type CFWL_ListBox::GetClassID() const {
40   return FWL_Type::ListBox;
41 }
42 
Update()43 void CFWL_ListBox::Update() {
44   if (IsLocked())
45     return;
46   if (!m_pProperties->m_pThemeProvider)
47     m_pProperties->m_pThemeProvider = GetAvailableTheme();
48 
49   switch (m_pProperties->m_dwStyleExes & FWL_STYLEEXT_LTB_AlignMask) {
50     case FWL_STYLEEXT_LTB_LeftAlign: {
51       m_iTTOAligns = FDE_TextAlignment::kCenterLeft;
52       break;
53     }
54     case FWL_STYLEEXT_LTB_RightAlign: {
55       m_iTTOAligns = FDE_TextAlignment::kCenterRight;
56       break;
57     }
58     case FWL_STYLEEXT_LTB_CenterAlign:
59     default: {
60       m_iTTOAligns = FDE_TextAlignment::kCenter;
61       break;
62     }
63   }
64   m_TTOStyles.single_line_ = true;
65   m_fScorllBarWidth = GetScrollWidth();
66   CalcSize(false);
67 }
68 
HitTest(const CFX_PointF & point)69 FWL_WidgetHit CFWL_ListBox::HitTest(const CFX_PointF& point) {
70   if (IsShowScrollBar(false)) {
71     CFX_RectF rect = m_pHorzScrollBar->GetWidgetRect();
72     if (rect.Contains(point))
73       return FWL_WidgetHit::HScrollBar;
74   }
75   if (IsShowScrollBar(true)) {
76     CFX_RectF rect = m_pVertScrollBar->GetWidgetRect();
77     if (rect.Contains(point))
78       return FWL_WidgetHit::VScrollBar;
79   }
80   if (m_rtClient.Contains(point))
81     return FWL_WidgetHit::Client;
82   return FWL_WidgetHit::Unknown;
83 }
84 
DrawWidget(CXFA_Graphics * pGraphics,const CFX_Matrix & matrix)85 void CFWL_ListBox::DrawWidget(CXFA_Graphics* pGraphics,
86                               const CFX_Matrix& matrix) {
87   if (!pGraphics)
88     return;
89 
90   IFWL_ThemeProvider* pTheme = m_pProperties->m_pThemeProvider.Get();
91   if (!pTheme)
92     return;
93 
94   pGraphics->SaveGraphState();
95   if (HasBorder())
96     DrawBorder(pGraphics, CFWL_Part::Border, pTheme, matrix);
97 
98   CFX_RectF rtClip(m_rtConent);
99   if (IsShowScrollBar(false))
100     rtClip.height -= m_fScorllBarWidth;
101   if (IsShowScrollBar(true))
102     rtClip.width -= m_fScorllBarWidth;
103 
104   pGraphics->SetClipRect(matrix.TransformRect(rtClip));
105   if ((m_pProperties->m_dwStyles & FWL_WGTSTYLE_NoBackground) == 0)
106     DrawBkground(pGraphics, pTheme, &matrix);
107 
108   DrawItems(pGraphics, pTheme, &matrix);
109   pGraphics->RestoreGraphState();
110 }
111 
SetThemeProvider(IFWL_ThemeProvider * pThemeProvider)112 void CFWL_ListBox::SetThemeProvider(IFWL_ThemeProvider* pThemeProvider) {
113   if (pThemeProvider)
114     m_pProperties->m_pThemeProvider = pThemeProvider;
115 }
116 
CountSelItems()117 int32_t CFWL_ListBox::CountSelItems() {
118   int32_t iRet = 0;
119   int32_t iCount = CountItems(this);
120   for (int32_t i = 0; i < iCount; i++) {
121     CFWL_ListItem* pItem = GetItem(this, i);
122     if (!pItem)
123       continue;
124     if (pItem->GetStates() & FWL_ITEMSTATE_LTB_Selected)
125       iRet++;
126   }
127   return iRet;
128 }
129 
GetSelItem(int32_t nIndexSel)130 CFWL_ListItem* CFWL_ListBox::GetSelItem(int32_t nIndexSel) {
131   int32_t idx = GetSelIndex(nIndexSel);
132   if (idx < 0)
133     return nullptr;
134   return GetItem(this, idx);
135 }
136 
GetSelIndex(int32_t nIndex)137 int32_t CFWL_ListBox::GetSelIndex(int32_t nIndex) {
138   int32_t index = 0;
139   int32_t iCount = CountItems(this);
140   for (int32_t i = 0; i < iCount; i++) {
141     CFWL_ListItem* pItem = GetItem(this, i);
142     if (!pItem)
143       return -1;
144     if (pItem->GetStates() & FWL_ITEMSTATE_LTB_Selected) {
145       if (index == nIndex)
146         return i;
147       index++;
148     }
149   }
150   return -1;
151 }
152 
SetSelItem(CFWL_ListItem * pItem,bool bSelect)153 void CFWL_ListBox::SetSelItem(CFWL_ListItem* pItem, bool bSelect) {
154   if (!pItem) {
155     if (bSelect) {
156       SelectAll();
157     } else {
158       ClearSelection();
159       SetFocusItem(nullptr);
160     }
161     return;
162   }
163   if (IsMultiSelection())
164     SetSelectionDirect(pItem, bSelect);
165   else
166     SetSelection(pItem, pItem, bSelect);
167 }
168 
GetListItem(CFWL_ListItem * pItem,uint32_t dwKeyCode)169 CFWL_ListItem* CFWL_ListBox::GetListItem(CFWL_ListItem* pItem,
170                                          uint32_t dwKeyCode) {
171   CFWL_ListItem* hRet = nullptr;
172   switch (dwKeyCode) {
173     case XFA_FWL_VKEY_Up:
174     case XFA_FWL_VKEY_Down:
175     case XFA_FWL_VKEY_Home:
176     case XFA_FWL_VKEY_End: {
177       const bool bUp = dwKeyCode == XFA_FWL_VKEY_Up;
178       const bool bDown = dwKeyCode == XFA_FWL_VKEY_Down;
179       const bool bHome = dwKeyCode == XFA_FWL_VKEY_Home;
180       int32_t iDstItem = -1;
181       if (bUp || bDown) {
182         int32_t index = GetItemIndex(this, pItem);
183         iDstItem = dwKeyCode == XFA_FWL_VKEY_Up ? index - 1 : index + 1;
184       } else if (bHome) {
185         iDstItem = 0;
186       } else {
187         int32_t iCount = CountItems(this);
188         iDstItem = iCount - 1;
189       }
190       hRet = GetItem(this, iDstItem);
191       break;
192     }
193     default:
194       break;
195   }
196   return hRet;
197 }
198 
SetSelection(CFWL_ListItem * hStart,CFWL_ListItem * hEnd,bool bSelected)199 void CFWL_ListBox::SetSelection(CFWL_ListItem* hStart,
200                                 CFWL_ListItem* hEnd,
201                                 bool bSelected) {
202   int32_t iStart = GetItemIndex(this, hStart);
203   int32_t iEnd = GetItemIndex(this, hEnd);
204   if (iStart > iEnd) {
205     int32_t iTemp = iStart;
206     iStart = iEnd;
207     iEnd = iTemp;
208   }
209   if (bSelected) {
210     int32_t iCount = CountItems(this);
211     for (int32_t i = 0; i < iCount; i++) {
212       CFWL_ListItem* pItem = GetItem(this, i);
213       SetSelectionDirect(pItem, false);
214     }
215   }
216   for (; iStart <= iEnd; iStart++) {
217     CFWL_ListItem* pItem = GetItem(this, iStart);
218     SetSelectionDirect(pItem, bSelected);
219   }
220 }
221 
SetSelectionDirect(CFWL_ListItem * pItem,bool bSelect)222 void CFWL_ListBox::SetSelectionDirect(CFWL_ListItem* pItem, bool bSelect) {
223   if (!pItem)
224     return;
225 
226   uint32_t dwOldStyle = pItem->GetStates();
227   bSelect ? dwOldStyle |= FWL_ITEMSTATE_LTB_Selected
228           : dwOldStyle &= ~FWL_ITEMSTATE_LTB_Selected;
229   pItem->SetStates(dwOldStyle);
230 }
231 
IsMultiSelection() const232 bool CFWL_ListBox::IsMultiSelection() const {
233   return m_pProperties->m_dwStyleExes & FWL_STYLEEXT_LTB_MultiSelection;
234 }
235 
IsItemSelected(CFWL_ListItem * pItem)236 bool CFWL_ListBox::IsItemSelected(CFWL_ListItem* pItem) {
237   return pItem && (pItem->GetStates() & FWL_ITEMSTATE_LTB_Selected) != 0;
238 }
239 
ClearSelection()240 void CFWL_ListBox::ClearSelection() {
241   bool bMulti = IsMultiSelection();
242   int32_t iCount = CountItems(this);
243   for (int32_t i = 0; i < iCount; i++) {
244     CFWL_ListItem* pItem = GetItem(this, i);
245     if (!pItem)
246       continue;
247     if (!(pItem->GetStates() & FWL_ITEMSTATE_LTB_Selected))
248       continue;
249     SetSelectionDirect(pItem, false);
250     if (!bMulti)
251       return;
252   }
253 }
254 
SelectAll()255 void CFWL_ListBox::SelectAll() {
256   if (!IsMultiSelection())
257     return;
258 
259   int32_t iCount = CountItems(this);
260   if (iCount <= 0)
261     return;
262 
263   CFWL_ListItem* pItemStart = GetItem(this, 0);
264   CFWL_ListItem* pItemEnd = GetItem(this, iCount - 1);
265   SetSelection(pItemStart, pItemEnd, false);
266 }
267 
GetFocusedItem()268 CFWL_ListItem* CFWL_ListBox::GetFocusedItem() {
269   int32_t iCount = CountItems(this);
270   for (int32_t i = 0; i < iCount; i++) {
271     CFWL_ListItem* pItem = GetItem(this, i);
272     if (!pItem)
273       return nullptr;
274     if (pItem->GetStates() & FWL_ITEMSTATE_LTB_Focused)
275       return pItem;
276   }
277   return nullptr;
278 }
279 
SetFocusItem(CFWL_ListItem * pItem)280 void CFWL_ListBox::SetFocusItem(CFWL_ListItem* pItem) {
281   CFWL_ListItem* hFocus = GetFocusedItem();
282   if (pItem == hFocus)
283     return;
284 
285   if (hFocus) {
286     uint32_t dwStyle = hFocus->GetStates();
287     dwStyle &= ~FWL_ITEMSTATE_LTB_Focused;
288     hFocus->SetStates(dwStyle);
289   }
290   if (pItem) {
291     uint32_t dwStyle = pItem->GetStates();
292     dwStyle |= FWL_ITEMSTATE_LTB_Focused;
293     pItem->SetStates(dwStyle);
294   }
295 }
296 
GetItemAtPoint(const CFX_PointF & point)297 CFWL_ListItem* CFWL_ListBox::GetItemAtPoint(const CFX_PointF& point) {
298   CFX_PointF pos = point - m_rtConent.TopLeft();
299   float fPosX = 0.0f;
300   if (m_pHorzScrollBar)
301     fPosX = m_pHorzScrollBar->GetPos();
302 
303   float fPosY = 0.0;
304   if (m_pVertScrollBar)
305     fPosY = m_pVertScrollBar->GetPos();
306 
307   int32_t nCount = CountItems(this);
308   for (int32_t i = 0; i < nCount; i++) {
309     CFWL_ListItem* pItem = GetItem(this, i);
310     if (!pItem)
311       continue;
312 
313     CFX_RectF rtItem = pItem->GetRect();
314     rtItem.Offset(-fPosX, -fPosY);
315     if (rtItem.Contains(pos))
316       return pItem;
317   }
318   return nullptr;
319 }
320 
ScrollToVisible(CFWL_ListItem * pItem)321 bool CFWL_ListBox::ScrollToVisible(CFWL_ListItem* pItem) {
322   if (!m_pVertScrollBar)
323     return false;
324 
325   CFX_RectF rtItem = pItem ? pItem->GetRect() : CFX_RectF();
326   bool bScroll = false;
327   float fPosY = m_pVertScrollBar->GetPos();
328   rtItem.Offset(0, -fPosY + m_rtConent.top);
329   if (rtItem.top < m_rtConent.top) {
330     fPosY += rtItem.top - m_rtConent.top;
331     bScroll = true;
332   } else if (rtItem.bottom() > m_rtConent.bottom()) {
333     fPosY += rtItem.bottom() - m_rtConent.bottom();
334     bScroll = true;
335   }
336   if (!bScroll)
337     return false;
338 
339   m_pVertScrollBar->SetPos(fPosY);
340   m_pVertScrollBar->SetTrackPos(fPosY);
341   RepaintRect(m_rtClient);
342   return true;
343 }
344 
DrawBkground(CXFA_Graphics * pGraphics,IFWL_ThemeProvider * pTheme,const CFX_Matrix * pMatrix)345 void CFWL_ListBox::DrawBkground(CXFA_Graphics* pGraphics,
346                                 IFWL_ThemeProvider* pTheme,
347                                 const CFX_Matrix* pMatrix) {
348   if (!pGraphics)
349     return;
350   if (!pTheme)
351     return;
352 
353   CFWL_ThemeBackground param;
354   param.m_pWidget = this;
355   param.m_iPart = CFWL_Part::Background;
356   param.m_dwStates = 0;
357   param.m_pGraphics = pGraphics;
358   param.m_matrix.Concat(*pMatrix);
359   param.m_rtPart = m_rtClient;
360   if (IsShowScrollBar(false) && IsShowScrollBar(true))
361     param.m_pRtData = &m_rtStatic;
362   if (!IsEnabled())
363     param.m_dwStates = CFWL_PartState_Disabled;
364 
365   pTheme->DrawBackground(param);
366 }
367 
DrawItems(CXFA_Graphics * pGraphics,IFWL_ThemeProvider * pTheme,const CFX_Matrix * pMatrix)368 void CFWL_ListBox::DrawItems(CXFA_Graphics* pGraphics,
369                              IFWL_ThemeProvider* pTheme,
370                              const CFX_Matrix* pMatrix) {
371   float fPosX = 0.0f;
372   if (m_pHorzScrollBar)
373     fPosX = m_pHorzScrollBar->GetPos();
374 
375   float fPosY = 0.0f;
376   if (m_pVertScrollBar)
377     fPosY = m_pVertScrollBar->GetPos();
378 
379   CFX_RectF rtView(m_rtConent);
380   if (m_pHorzScrollBar)
381     rtView.height -= m_fScorllBarWidth;
382   if (m_pVertScrollBar)
383     rtView.width -= m_fScorllBarWidth;
384 
385   int32_t iCount = CountItems(this);
386   for (int32_t i = 0; i < iCount; i++) {
387     CFWL_ListItem* pItem = GetItem(this, i);
388     if (!pItem)
389       continue;
390 
391     CFX_RectF rtItem = pItem->GetRect();
392     rtItem.Offset(m_rtConent.left - fPosX, m_rtConent.top - fPosY);
393     if (rtItem.bottom() < m_rtConent.top)
394       continue;
395     if (rtItem.top >= m_rtConent.bottom())
396       break;
397     DrawItem(pGraphics, pTheme, pItem, i, rtItem, pMatrix);
398   }
399 }
400 
DrawItem(CXFA_Graphics * pGraphics,IFWL_ThemeProvider * pTheme,CFWL_ListItem * pItem,int32_t Index,const CFX_RectF & rtItem,const CFX_Matrix * pMatrix)401 void CFWL_ListBox::DrawItem(CXFA_Graphics* pGraphics,
402                             IFWL_ThemeProvider* pTheme,
403                             CFWL_ListItem* pItem,
404                             int32_t Index,
405                             const CFX_RectF& rtItem,
406                             const CFX_Matrix* pMatrix) {
407   uint32_t dwItemStyles = pItem ? pItem->GetStates() : 0;
408   uint32_t dwPartStates = CFWL_PartState_Normal;
409   if (m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled)
410     dwPartStates = CFWL_PartState_Disabled;
411   else if (dwItemStyles & FWL_ITEMSTATE_LTB_Selected)
412     dwPartStates = CFWL_PartState_Selected;
413 
414   if (m_pProperties->m_dwStates & FWL_WGTSTATE_Focused &&
415       dwItemStyles & FWL_ITEMSTATE_LTB_Focused) {
416     dwPartStates |= CFWL_PartState_Focused;
417   }
418 
419   CFWL_ThemeBackground bg_param;
420   bg_param.m_pWidget = this;
421   bg_param.m_iPart = CFWL_Part::ListItem;
422   bg_param.m_dwStates = dwPartStates;
423   bg_param.m_pGraphics = pGraphics;
424   bg_param.m_matrix.Concat(*pMatrix);
425   bg_param.m_rtPart = rtItem;
426   bg_param.m_bMaximize = true;
427   CFX_RectF rtFocus(rtItem);
428   bg_param.m_pRtData = &rtFocus;
429   if (m_pVertScrollBar && !m_pHorzScrollBar &&
430       (dwPartStates & CFWL_PartState_Focused)) {
431     bg_param.m_rtPart.left += 1;
432     bg_param.m_rtPart.width -= (m_fScorllBarWidth + 1);
433     rtFocus.Deflate(0.5, 0.5, 1 + m_fScorllBarWidth, 1);
434   }
435   pTheme->DrawBackground(bg_param);
436 
437   if (!pItem)
438     return;
439 
440   WideString wsText = pItem->GetText();
441   if (wsText.GetLength() <= 0)
442     return;
443 
444   CFX_RectF rtText(rtItem);
445   rtText.Deflate(kItemTextMargin, kItemTextMargin);
446 
447   CFWL_ThemeText textParam;
448   textParam.m_pWidget = this;
449   textParam.m_iPart = CFWL_Part::ListItem;
450   textParam.m_dwStates = dwPartStates;
451   textParam.m_pGraphics = pGraphics;
452   textParam.m_matrix.Concat(*pMatrix);
453   textParam.m_rtPart = rtText;
454   textParam.m_wsText = std::move(wsText);
455   textParam.m_dwTTOStyles = m_TTOStyles;
456   textParam.m_iTTOAlign = m_iTTOAligns;
457   textParam.m_bMaximize = true;
458   pTheme->DrawText(textParam);
459 }
460 
CalcSize(bool bAutoSize)461 CFX_SizeF CFWL_ListBox::CalcSize(bool bAutoSize) {
462   if (!m_pProperties->m_pThemeProvider)
463     return CFX_SizeF();
464 
465   m_rtClient = GetClientRect();
466   m_rtConent = m_rtClient;
467   CFX_RectF rtUIMargin;
468   if (!m_pOuter) {
469     CFWL_ThemePart part;
470     part.m_pWidget = this;
471     IFWL_ThemeProvider* theme = GetAvailableTheme();
472     CFX_RectF pUIMargin = theme ? theme->GetUIMargin(part) : CFX_RectF();
473     m_rtConent.Deflate(pUIMargin.left, pUIMargin.top, pUIMargin.width,
474                        pUIMargin.height);
475   }
476 
477   float fWidth = GetMaxTextWidth();
478   fWidth += 2 * kItemTextMargin;
479   if (!bAutoSize) {
480     float fActualWidth = m_rtClient.width - rtUIMargin.left - rtUIMargin.width;
481     fWidth = std::max(fWidth, fActualWidth);
482   }
483   m_fItemHeight = CalcItemHeight();
484 
485   int32_t iCount = CountItems(this);
486   CFX_SizeF fs;
487   for (int32_t i = 0; i < iCount; i++) {
488     CFWL_ListItem* htem = GetItem(this, i);
489     UpdateItemSize(htem, fs, fWidth, m_fItemHeight, bAutoSize);
490   }
491   if (bAutoSize)
492     return fs;
493 
494   float iHeight = m_rtClient.height;
495   bool bShowVertScr = false;
496   bool bShowHorzScr = false;
497   if (!bShowVertScr && (m_pProperties->m_dwStyles & FWL_WGTSTYLE_VScroll))
498     bShowVertScr = (fs.height > iHeight);
499 
500   CFX_SizeF szRange;
501   if (bShowVertScr) {
502     if (!m_pVertScrollBar)
503       InitVerticalScrollBar();
504 
505     CFX_RectF rtScrollBar(m_rtClient.right() - m_fScorllBarWidth,
506                           m_rtClient.top, m_fScorllBarWidth,
507                           m_rtClient.height - 1);
508     if (bShowHorzScr)
509       rtScrollBar.height -= m_fScorllBarWidth;
510 
511     m_pVertScrollBar->SetWidgetRect(rtScrollBar);
512     szRange.width = 0;
513     szRange.height = std::max(fs.height - m_rtConent.height, m_fItemHeight);
514 
515     m_pVertScrollBar->SetRange(szRange.width, szRange.height);
516     m_pVertScrollBar->SetPageSize(rtScrollBar.height * 9 / 10);
517     m_pVertScrollBar->SetStepSize(m_fItemHeight);
518 
519     float fPos =
520         pdfium::clamp(m_pVertScrollBar->GetPos(), 0.0f, szRange.height);
521     m_pVertScrollBar->SetPos(fPos);
522     m_pVertScrollBar->SetTrackPos(fPos);
523     if ((m_pProperties->m_dwStyleExes & FWL_STYLEEXT_LTB_ShowScrollBarFocus) ==
524             0 ||
525         (m_pProperties->m_dwStates & FWL_WGTSTATE_Focused)) {
526       m_pVertScrollBar->RemoveStates(FWL_WGTSTATE_Invisible);
527     }
528     m_pVertScrollBar->Update();
529   } else if (m_pVertScrollBar) {
530     m_pVertScrollBar->SetPos(0);
531     m_pVertScrollBar->SetTrackPos(0);
532     m_pVertScrollBar->SetStates(FWL_WGTSTATE_Invisible);
533   }
534   if (bShowHorzScr) {
535     if (!m_pHorzScrollBar)
536       InitHorizontalScrollBar();
537 
538     CFX_RectF rtScrollBar(m_rtClient.left,
539                           m_rtClient.bottom() - m_fScorllBarWidth,
540                           m_rtClient.width, m_fScorllBarWidth);
541     if (bShowVertScr)
542       rtScrollBar.width -= m_fScorllBarWidth;
543 
544     m_pHorzScrollBar->SetWidgetRect(rtScrollBar);
545     szRange.width = 0;
546     szRange.height = fs.width - rtScrollBar.width;
547     m_pHorzScrollBar->SetRange(szRange.width, szRange.height);
548     m_pHorzScrollBar->SetPageSize(fWidth * 9 / 10);
549     m_pHorzScrollBar->SetStepSize(fWidth / 10);
550 
551     float fPos =
552         pdfium::clamp(m_pHorzScrollBar->GetPos(), 0.0f, szRange.height);
553     m_pHorzScrollBar->SetPos(fPos);
554     m_pHorzScrollBar->SetTrackPos(fPos);
555     if ((m_pProperties->m_dwStyleExes & FWL_STYLEEXT_LTB_ShowScrollBarFocus) ==
556             0 ||
557         (m_pProperties->m_dwStates & FWL_WGTSTATE_Focused)) {
558       m_pHorzScrollBar->RemoveStates(FWL_WGTSTATE_Invisible);
559     }
560     m_pHorzScrollBar->Update();
561   } else if (m_pHorzScrollBar) {
562     m_pHorzScrollBar->SetPos(0);
563     m_pHorzScrollBar->SetTrackPos(0);
564     m_pHorzScrollBar->SetStates(FWL_WGTSTATE_Invisible);
565   }
566   if (bShowVertScr && bShowHorzScr) {
567     m_rtStatic = CFX_RectF(m_rtClient.right() - m_fScorllBarWidth,
568                            m_rtClient.bottom() - m_fScorllBarWidth,
569                            m_fScorllBarWidth, m_fScorllBarWidth);
570   }
571   return fs;
572 }
573 
UpdateItemSize(CFWL_ListItem * pItem,CFX_SizeF & size,float fWidth,float fItemHeight,bool bAutoSize) const574 void CFWL_ListBox::UpdateItemSize(CFWL_ListItem* pItem,
575                                   CFX_SizeF& size,
576                                   float fWidth,
577                                   float fItemHeight,
578                                   bool bAutoSize) const {
579   if (!bAutoSize && pItem) {
580     CFX_RectF rtItem(0, size.height, fWidth, fItemHeight);
581     pItem->SetRect(rtItem);
582   }
583   size.width = fWidth;
584   size.height += fItemHeight;
585 }
586 
GetMaxTextWidth()587 float CFWL_ListBox::GetMaxTextWidth() {
588   float fRet = 0.0f;
589   int32_t iCount = CountItems(this);
590   for (int32_t i = 0; i < iCount; i++) {
591     CFWL_ListItem* pItem = GetItem(this, i);
592     if (!pItem)
593       continue;
594 
595     CFX_SizeF sz = CalcTextSize(pItem->GetText(),
596                                 m_pProperties->m_pThemeProvider.Get(), false);
597     fRet = std::max(fRet, sz.width);
598   }
599   return fRet;
600 }
601 
GetScrollWidth()602 float CFWL_ListBox::GetScrollWidth() {
603   IFWL_ThemeProvider* theme = GetAvailableTheme();
604   return theme ? theme->GetScrollBarWidth() : 0.0f;
605 }
606 
CalcItemHeight()607 float CFWL_ListBox::CalcItemHeight() {
608   IFWL_ThemeProvider* theme = GetAvailableTheme();
609   CFWL_ThemePart part;
610   part.m_pWidget = this;
611   return (theme ? theme->GetFontSize(part) : 20.0f) + 2 * kItemTextMargin;
612 }
613 
InitVerticalScrollBar()614 void CFWL_ListBox::InitVerticalScrollBar() {
615   if (m_pVertScrollBar)
616     return;
617 
618   auto prop = pdfium::MakeUnique<CFWL_WidgetProperties>();
619   prop->m_dwStyleExes = FWL_STYLEEXT_SCB_Vert;
620   prop->m_dwStates = FWL_WGTSTATE_Invisible;
621   prop->m_pParent = this;
622   prop->m_pThemeProvider = m_pScrollBarTP;
623   m_pVertScrollBar = pdfium::MakeUnique<CFWL_ScrollBar>(m_pOwnerApp.Get(),
624                                                         std::move(prop), this);
625 }
626 
InitHorizontalScrollBar()627 void CFWL_ListBox::InitHorizontalScrollBar() {
628   if (m_pHorzScrollBar)
629     return;
630 
631   auto prop = pdfium::MakeUnique<CFWL_WidgetProperties>();
632   prop->m_dwStyleExes = FWL_STYLEEXT_SCB_Horz;
633   prop->m_dwStates = FWL_WGTSTATE_Invisible;
634   prop->m_pParent = this;
635   prop->m_pThemeProvider = m_pScrollBarTP;
636   m_pHorzScrollBar = pdfium::MakeUnique<CFWL_ScrollBar>(m_pOwnerApp.Get(),
637                                                         std::move(prop), this);
638 }
639 
IsShowScrollBar(bool bVert)640 bool CFWL_ListBox::IsShowScrollBar(bool bVert) {
641   CFWL_ScrollBar* pScrollbar =
642       bVert ? m_pVertScrollBar.get() : m_pHorzScrollBar.get();
643   if (!pScrollbar || !pScrollbar->IsVisible())
644     return false;
645 
646   return !(m_pProperties->m_dwStyleExes &
647            FWL_STYLEEXT_LTB_ShowScrollBarFocus) ||
648          (m_pProperties->m_dwStates & FWL_WGTSTATE_Focused);
649 }
650 
OnProcessMessage(CFWL_Message * pMessage)651 void CFWL_ListBox::OnProcessMessage(CFWL_Message* pMessage) {
652   if (!pMessage)
653     return;
654   if (!IsEnabled())
655     return;
656 
657   switch (pMessage->GetType()) {
658     case CFWL_Message::Type::SetFocus:
659       OnFocusChanged(pMessage, true);
660       break;
661     case CFWL_Message::Type::KillFocus:
662       OnFocusChanged(pMessage, false);
663       break;
664     case CFWL_Message::Type::Mouse: {
665       CFWL_MessageMouse* pMsg = static_cast<CFWL_MessageMouse*>(pMessage);
666       switch (pMsg->m_dwCmd) {
667         case FWL_MouseCommand::LeftButtonDown:
668           OnLButtonDown(pMsg);
669           break;
670         case FWL_MouseCommand::LeftButtonUp:
671           OnLButtonUp(pMsg);
672           break;
673         default:
674           break;
675       }
676       break;
677     }
678     case CFWL_Message::Type::MouseWheel:
679       OnMouseWheel(static_cast<CFWL_MessageMouseWheel*>(pMessage));
680       break;
681     case CFWL_Message::Type::Key: {
682       CFWL_MessageKey* pMsg = static_cast<CFWL_MessageKey*>(pMessage);
683       if (pMsg->m_dwCmd == FWL_KeyCommand::KeyDown)
684         OnKeyDown(pMsg);
685       break;
686     }
687     default:
688       break;
689   }
690   // Dst target could be |this|, continue only if not destroyed by above.
691   if (pMessage->GetDstTarget())
692     CFWL_Widget::OnProcessMessage(pMessage);
693 }
694 
OnProcessEvent(CFWL_Event * pEvent)695 void CFWL_ListBox::OnProcessEvent(CFWL_Event* pEvent) {
696   if (!pEvent)
697     return;
698   if (pEvent->GetType() != CFWL_Event::Type::Scroll)
699     return;
700 
701   CFWL_Widget* pSrcTarget = pEvent->GetSrcTarget();
702   if ((pSrcTarget == m_pVertScrollBar.get() && m_pVertScrollBar) ||
703       (pSrcTarget == m_pHorzScrollBar.get() && m_pHorzScrollBar)) {
704     CFWL_EventScroll* pScrollEvent = static_cast<CFWL_EventScroll*>(pEvent);
705     OnScroll(static_cast<CFWL_ScrollBar*>(pSrcTarget),
706              pScrollEvent->m_iScrollCode, pScrollEvent->m_fPos);
707   }
708 }
709 
OnDrawWidget(CXFA_Graphics * pGraphics,const CFX_Matrix & matrix)710 void CFWL_ListBox::OnDrawWidget(CXFA_Graphics* pGraphics,
711                                 const CFX_Matrix& matrix) {
712   DrawWidget(pGraphics, matrix);
713 }
714 
OnFocusChanged(CFWL_Message * pMsg,bool bSet)715 void CFWL_ListBox::OnFocusChanged(CFWL_Message* pMsg, bool bSet) {
716   if (GetStylesEx() & FWL_STYLEEXT_LTB_ShowScrollBarFocus) {
717     if (m_pVertScrollBar) {
718       if (bSet)
719         m_pVertScrollBar->RemoveStates(FWL_WGTSTATE_Invisible);
720       else
721         m_pVertScrollBar->SetStates(FWL_WGTSTATE_Invisible);
722     }
723     if (m_pHorzScrollBar) {
724       if (bSet)
725         m_pHorzScrollBar->RemoveStates(FWL_WGTSTATE_Invisible);
726       else
727         m_pHorzScrollBar->SetStates(FWL_WGTSTATE_Invisible);
728     }
729   }
730   if (bSet)
731     m_pProperties->m_dwStates |= (FWL_WGTSTATE_Focused);
732   else
733     m_pProperties->m_dwStates &= ~(FWL_WGTSTATE_Focused);
734 
735   RepaintRect(m_rtClient);
736 }
737 
OnLButtonDown(CFWL_MessageMouse * pMsg)738 void CFWL_ListBox::OnLButtonDown(CFWL_MessageMouse* pMsg) {
739   m_bLButtonDown = true;
740 
741   CFWL_ListItem* pItem = GetItemAtPoint(pMsg->m_pos);
742   if (!pItem)
743     return;
744 
745   if (IsMultiSelection()) {
746     if (pMsg->m_dwFlags & FWL_KEYFLAG_Ctrl) {
747       bool bSelected = IsItemSelected(pItem);
748       SetSelectionDirect(pItem, !bSelected);
749       m_hAnchor = pItem;
750     } else if (pMsg->m_dwFlags & FWL_KEYFLAG_Shift) {
751       if (m_hAnchor)
752         SetSelection(m_hAnchor, pItem, true);
753       else
754         SetSelectionDirect(pItem, true);
755     } else {
756       SetSelection(pItem, pItem, true);
757       m_hAnchor = pItem;
758     }
759   } else {
760     SetSelection(pItem, pItem, true);
761   }
762 
763   SetFocusItem(pItem);
764   ScrollToVisible(pItem);
765   SetGrab(true);
766   RepaintRect(m_rtClient);
767 }
768 
OnLButtonUp(CFWL_MessageMouse * pMsg)769 void CFWL_ListBox::OnLButtonUp(CFWL_MessageMouse* pMsg) {
770   if (!m_bLButtonDown)
771     return;
772 
773   m_bLButtonDown = false;
774   SetGrab(false);
775 }
776 
OnMouseWheel(CFWL_MessageMouseWheel * pMsg)777 void CFWL_ListBox::OnMouseWheel(CFWL_MessageMouseWheel* pMsg) {
778   if (IsShowScrollBar(true))
779     m_pVertScrollBar->GetDelegate()->OnProcessMessage(pMsg);
780 }
781 
OnKeyDown(CFWL_MessageKey * pMsg)782 void CFWL_ListBox::OnKeyDown(CFWL_MessageKey* pMsg) {
783   uint32_t dwKeyCode = pMsg->m_dwKeyCode;
784   switch (dwKeyCode) {
785     case XFA_FWL_VKEY_Tab:
786     case XFA_FWL_VKEY_Up:
787     case XFA_FWL_VKEY_Down:
788     case XFA_FWL_VKEY_Home:
789     case XFA_FWL_VKEY_End: {
790       CFWL_ListItem* pItem = GetFocusedItem();
791       pItem = GetListItem(pItem, dwKeyCode);
792       bool bShift = !!(pMsg->m_dwFlags & FWL_KEYFLAG_Shift);
793       bool bCtrl = !!(pMsg->m_dwFlags & FWL_KEYFLAG_Ctrl);
794       OnVK(pItem, bShift, bCtrl);
795       break;
796     }
797     default:
798       break;
799   }
800 }
801 
OnVK(CFWL_ListItem * pItem,bool bShift,bool bCtrl)802 void CFWL_ListBox::OnVK(CFWL_ListItem* pItem, bool bShift, bool bCtrl) {
803   if (!pItem)
804     return;
805 
806   if (IsMultiSelection()) {
807     if (bCtrl) {
808       // Do nothing.
809     } else if (bShift) {
810       if (m_hAnchor)
811         SetSelection(m_hAnchor, pItem, true);
812       else
813         SetSelectionDirect(pItem, true);
814     } else {
815       SetSelection(pItem, pItem, true);
816       m_hAnchor = pItem;
817     }
818   } else {
819     SetSelection(pItem, pItem, true);
820   }
821 
822   SetFocusItem(pItem);
823   ScrollToVisible(pItem);
824 
825   RepaintRect(CFX_RectF(0, 0, m_pProperties->m_rtWidget.width,
826                         m_pProperties->m_rtWidget.height));
827 }
828 
OnScroll(CFWL_ScrollBar * pScrollBar,CFWL_EventScroll::Code dwCode,float fPos)829 bool CFWL_ListBox::OnScroll(CFWL_ScrollBar* pScrollBar,
830                             CFWL_EventScroll::Code dwCode,
831                             float fPos) {
832   CFX_SizeF fs;
833   pScrollBar->GetRange(&fs.width, &fs.height);
834   float iCurPos = pScrollBar->GetPos();
835   float fStep = pScrollBar->GetStepSize();
836   switch (dwCode) {
837     case CFWL_EventScroll::Code::Min: {
838       fPos = fs.width;
839       break;
840     }
841     case CFWL_EventScroll::Code::Max: {
842       fPos = fs.height;
843       break;
844     }
845     case CFWL_EventScroll::Code::StepBackward: {
846       fPos -= fStep;
847       if (fPos < fs.width + fStep / 2)
848         fPos = fs.width;
849       break;
850     }
851     case CFWL_EventScroll::Code::StepForward: {
852       fPos += fStep;
853       if (fPos > fs.height - fStep / 2)
854         fPos = fs.height;
855       break;
856     }
857     case CFWL_EventScroll::Code::PageBackward: {
858       fPos -= pScrollBar->GetPageSize();
859       if (fPos < fs.width)
860         fPos = fs.width;
861       break;
862     }
863     case CFWL_EventScroll::Code::PageForward: {
864       fPos += pScrollBar->GetPageSize();
865       if (fPos > fs.height)
866         fPos = fs.height;
867       break;
868     }
869     case CFWL_EventScroll::Code::Pos:
870     case CFWL_EventScroll::Code::TrackPos:
871     case CFWL_EventScroll::Code::None:
872       break;
873     case CFWL_EventScroll::Code::EndScroll:
874       return false;
875   }
876   if (iCurPos != fPos) {
877     pScrollBar->SetPos(fPos);
878     pScrollBar->SetTrackPos(fPos);
879     RepaintRect(m_rtClient);
880   }
881   return true;
882 }
883 
CountItems(const CFWL_Widget * pWidget) const884 int32_t CFWL_ListBox::CountItems(const CFWL_Widget* pWidget) const {
885   return pdfium::CollectionSize<int32_t>(m_ItemArray);
886 }
887 
GetItem(const CFWL_Widget * pWidget,int32_t nIndex) const888 CFWL_ListItem* CFWL_ListBox::GetItem(const CFWL_Widget* pWidget,
889                                      int32_t nIndex) const {
890   if (nIndex < 0 || nIndex >= CountItems(pWidget))
891     return nullptr;
892   return m_ItemArray[nIndex].get();
893 }
894 
GetItemIndex(CFWL_Widget * pWidget,CFWL_ListItem * pItem)895 int32_t CFWL_ListBox::GetItemIndex(CFWL_Widget* pWidget, CFWL_ListItem* pItem) {
896   auto it =
897       std::find_if(m_ItemArray.begin(), m_ItemArray.end(),
898                    [pItem](const std::unique_ptr<CFWL_ListItem>& candidate) {
899                      return candidate.get() == pItem;
900                    });
901   return it != m_ItemArray.end() ? it - m_ItemArray.begin() : -1;
902 }
903 
AddString(const WideString & wsAdd)904 CFWL_ListItem* CFWL_ListBox::AddString(const WideString& wsAdd) {
905   m_ItemArray.emplace_back(pdfium::MakeUnique<CFWL_ListItem>(wsAdd));
906   return m_ItemArray.back().get();
907 }
908 
RemoveAt(int32_t iIndex)909 void CFWL_ListBox::RemoveAt(int32_t iIndex) {
910   if (iIndex < 0 || static_cast<size_t>(iIndex) >= m_ItemArray.size())
911     return;
912   m_ItemArray.erase(m_ItemArray.begin() + iIndex);
913 }
914 
DeleteString(CFWL_ListItem * pItem)915 void CFWL_ListBox::DeleteString(CFWL_ListItem* pItem) {
916   int32_t nIndex = GetItemIndex(this, pItem);
917   if (nIndex < 0 || static_cast<size_t>(nIndex) >= m_ItemArray.size())
918     return;
919 
920   int32_t iSel = nIndex + 1;
921   if (iSel >= CountItems(this))
922     iSel = nIndex - 1;
923   if (iSel >= 0) {
924     if (CFWL_ListItem* item = GetItem(this, iSel))
925       item->SetStates(item->GetStates() | FWL_ITEMSTATE_LTB_Selected);
926   }
927 
928   m_ItemArray.erase(m_ItemArray.begin() + nIndex);
929 }
930 
DeleteAll()931 void CFWL_ListBox::DeleteAll() {
932   m_ItemArray.clear();
933 }
934