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 "fpdfsdk/pdfwindow/PWL_ListBox.h"
8
9#include "fpdfsdk/fxedit/fxet_edit.h"
10#include "fpdfsdk/fxedit/fxet_list.h"
11#include "fpdfsdk/pdfwindow/PWL_Edit.h"
12#include "fpdfsdk/pdfwindow/PWL_EditCtrl.h"
13#include "fpdfsdk/pdfwindow/PWL_ScrollBar.h"
14#include "fpdfsdk/pdfwindow/PWL_Utils.h"
15#include "fpdfsdk/pdfwindow/PWL_Wnd.h"
16#include "public/fpdf_fwlevent.h"
17#include "third_party/base/ptr_util.h"
18
19CPWL_List_Notify::CPWL_List_Notify(CPWL_ListBox* pList) : m_pList(pList) {
20  ASSERT(m_pList);
21}
22
23CPWL_List_Notify::~CPWL_List_Notify() {}
24
25void CPWL_List_Notify::IOnSetScrollInfoY(FX_FLOAT fPlateMin,
26                                         FX_FLOAT fPlateMax,
27                                         FX_FLOAT fContentMin,
28                                         FX_FLOAT fContentMax,
29                                         FX_FLOAT fSmallStep,
30                                         FX_FLOAT fBigStep) {
31  PWL_SCROLL_INFO Info;
32
33  Info.fPlateWidth = fPlateMax - fPlateMin;
34  Info.fContentMin = fContentMin;
35  Info.fContentMax = fContentMax;
36  Info.fSmallStep = fSmallStep;
37  Info.fBigStep = fBigStep;
38
39  m_pList->OnNotify(m_pList, PNM_SETSCROLLINFO, SBT_VSCROLL, (intptr_t)&Info);
40
41  if (CPWL_ScrollBar* pScroll = m_pList->GetVScrollBar()) {
42    if (IsFloatBigger(Info.fPlateWidth, Info.fContentMax - Info.fContentMin) ||
43        IsFloatEqual(Info.fPlateWidth, Info.fContentMax - Info.fContentMin)) {
44      if (pScroll->IsVisible()) {
45        pScroll->SetVisible(false);
46        m_pList->RePosChildWnd();
47      }
48    } else {
49      if (!pScroll->IsVisible()) {
50        pScroll->SetVisible(true);
51        m_pList->RePosChildWnd();
52      }
53    }
54  }
55}
56
57void CPWL_List_Notify::IOnSetScrollPosY(FX_FLOAT fy) {
58  m_pList->OnNotify(m_pList, PNM_SETSCROLLPOS, SBT_VSCROLL, (intptr_t)&fy);
59}
60
61void CPWL_List_Notify::IOnInvalidateRect(CFX_FloatRect* pRect) {
62  m_pList->InvalidateRect(pRect);
63}
64
65CPWL_ListBox::CPWL_ListBox()
66    : m_pList(new CFX_ListCtrl),
67      m_bMouseDown(false),
68      m_bHoverSel(false),
69      m_pFillerNotify(nullptr) {}
70
71CPWL_ListBox::~CPWL_ListBox() {
72}
73
74CFX_ByteString CPWL_ListBox::GetClassName() const {
75  return "CPWL_ListBox";
76}
77
78void CPWL_ListBox::OnCreated() {
79  m_pList->SetFontMap(GetFontMap());
80  m_pListNotify = pdfium::MakeUnique<CPWL_List_Notify>(this);
81  m_pList->SetNotify(m_pListNotify.get());
82
83  SetHoverSel(HasFlag(PLBS_HOVERSEL));
84  m_pList->SetMultipleSel(HasFlag(PLBS_MULTIPLESEL));
85  m_pList->SetFontSize(GetCreationParam().fFontSize);
86
87  m_bHoverSel = HasFlag(PLBS_HOVERSEL);
88}
89
90void CPWL_ListBox::OnDestroy() {
91  // Make sure the notifier is removed from the list as we are about to
92  // destroy the notifier and don't want to leave a dangling pointer.
93  m_pList->SetNotify(nullptr);
94  m_pListNotify.reset();
95}
96
97void CPWL_ListBox::GetThisAppearanceStream(CFX_ByteTextBuf& sAppStream) {
98  CPWL_Wnd::GetThisAppearanceStream(sAppStream);
99
100  CFX_ByteTextBuf sListItems;
101
102  CFX_FloatRect rcPlate = m_pList->GetPlateRect();
103  for (int32_t i = 0, sz = m_pList->GetCount(); i < sz; i++) {
104    CFX_FloatRect rcItem = m_pList->GetItemRect(i);
105
106    if (rcItem.bottom > rcPlate.top || rcItem.top < rcPlate.bottom)
107      continue;
108
109    CFX_PointF ptOffset(rcItem.left, (rcItem.top + rcItem.bottom) * 0.5f);
110    if (m_pList->IsItemSelected(i)) {
111      sListItems << CPWL_Utils::GetRectFillAppStream(rcItem,
112                                                     PWL_DEFAULT_SELBACKCOLOR)
113                        .AsStringC();
114      CFX_ByteString sItem =
115          CPWL_Utils::GetEditAppStream(m_pList->GetItemEdit(i), ptOffset);
116      if (sItem.GetLength() > 0) {
117        sListItems << "BT\n"
118                   << CPWL_Utils::GetColorAppStream(PWL_DEFAULT_SELTEXTCOLOR)
119                          .AsStringC()
120                   << sItem.AsStringC() << "ET\n";
121      }
122    } else {
123      CFX_ByteString sItem =
124          CPWL_Utils::GetEditAppStream(m_pList->GetItemEdit(i), ptOffset);
125      if (sItem.GetLength() > 0) {
126        sListItems << "BT\n"
127                   << CPWL_Utils::GetColorAppStream(GetTextColor()).AsStringC()
128                   << sItem.AsStringC() << "ET\n";
129      }
130    }
131  }
132
133  if (sListItems.GetLength() > 0) {
134    CFX_ByteTextBuf sClip;
135    CFX_FloatRect rcClient = GetClientRect();
136
137    sClip << "q\n";
138    sClip << rcClient.left << " " << rcClient.bottom << " "
139          << rcClient.right - rcClient.left << " "
140          << rcClient.top - rcClient.bottom << " re W n\n";
141
142    sClip << sListItems << "Q\n";
143
144    sAppStream << "/Tx BMC\n" << sClip << "EMC\n";
145  }
146}
147
148void CPWL_ListBox::DrawThisAppearance(CFX_RenderDevice* pDevice,
149                                      CFX_Matrix* pUser2Device) {
150  CPWL_Wnd::DrawThisAppearance(pDevice, pUser2Device);
151
152  CFX_FloatRect rcPlate = m_pList->GetPlateRect();
153  CFX_FloatRect rcList = GetListRect();
154  CFX_FloatRect rcClient = GetClientRect();
155
156  for (int32_t i = 0, sz = m_pList->GetCount(); i < sz; i++) {
157    CFX_FloatRect rcItem = m_pList->GetItemRect(i);
158    if (rcItem.bottom > rcPlate.top || rcItem.top < rcPlate.bottom)
159      continue;
160
161    CFX_PointF ptOffset(rcItem.left, (rcItem.top + rcItem.bottom) * 0.5f);
162    if (CFX_Edit* pEdit = m_pList->GetItemEdit(i)) {
163      CFX_FloatRect rcContent = pEdit->GetContentRect();
164      if (rcContent.Width() > rcClient.Width())
165        rcItem.Intersect(rcList);
166      else
167        rcItem.Intersect(rcClient);
168    }
169
170    if (m_pList->IsItemSelected(i)) {
171      CFX_SystemHandler* pSysHandler = GetSystemHandler();
172      if (pSysHandler && pSysHandler->IsSelectionImplemented()) {
173        CFX_Edit::DrawEdit(pDevice, pUser2Device, m_pList->GetItemEdit(i),
174                           GetTextColor().ToFXColor(255), rcList, ptOffset,
175                           nullptr, pSysHandler, m_pFormFiller);
176        pSysHandler->OutputSelectedRect(m_pFormFiller, rcItem);
177      } else {
178        CPWL_Utils::DrawFillRect(pDevice, pUser2Device, rcItem,
179                                 ArgbEncode(255, 0, 51, 113));
180        CFX_Edit::DrawEdit(pDevice, pUser2Device, m_pList->GetItemEdit(i),
181                           ArgbEncode(255, 255, 255, 255), rcList, ptOffset,
182                           nullptr, pSysHandler, m_pFormFiller);
183      }
184    } else {
185      CFX_SystemHandler* pSysHandler = GetSystemHandler();
186      CFX_Edit::DrawEdit(pDevice, pUser2Device, m_pList->GetItemEdit(i),
187                         GetTextColor().ToFXColor(255), rcList, ptOffset,
188                         nullptr, pSysHandler, nullptr);
189    }
190  }
191}
192
193bool CPWL_ListBox::OnKeyDown(uint16_t nChar, uint32_t nFlag) {
194  CPWL_Wnd::OnKeyDown(nChar, nFlag);
195
196  switch (nChar) {
197    default:
198      return false;
199    case FWL_VKEY_Up:
200    case FWL_VKEY_Down:
201    case FWL_VKEY_Home:
202    case FWL_VKEY_Left:
203    case FWL_VKEY_End:
204    case FWL_VKEY_Right:
205      break;
206  }
207
208  switch (nChar) {
209    case FWL_VKEY_Up:
210      m_pList->OnVK_UP(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
211      break;
212    case FWL_VKEY_Down:
213      m_pList->OnVK_DOWN(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
214      break;
215    case FWL_VKEY_Home:
216      m_pList->OnVK_HOME(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
217      break;
218    case FWL_VKEY_Left:
219      m_pList->OnVK_LEFT(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
220      break;
221    case FWL_VKEY_End:
222      m_pList->OnVK_END(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
223      break;
224    case FWL_VKEY_Right:
225      m_pList->OnVK_RIGHT(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
226      break;
227    case FWL_VKEY_Delete:
228      break;
229  }
230
231  bool bExit = false;
232  OnNotifySelChanged(true, bExit, nFlag);
233
234  return true;
235}
236
237bool CPWL_ListBox::OnChar(uint16_t nChar, uint32_t nFlag) {
238  CPWL_Wnd::OnChar(nChar, nFlag);
239
240  if (!m_pList->OnChar(nChar, IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag)))
241    return false;
242
243  bool bExit = false;
244  OnNotifySelChanged(true, bExit, nFlag);
245
246  return true;
247}
248
249bool CPWL_ListBox::OnLButtonDown(const CFX_PointF& point, uint32_t nFlag) {
250  CPWL_Wnd::OnLButtonDown(point, nFlag);
251
252  if (ClientHitTest(point)) {
253    m_bMouseDown = true;
254    SetFocus();
255    SetCapture();
256
257    m_pList->OnMouseDown(point, IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
258  }
259
260  return true;
261}
262
263bool CPWL_ListBox::OnLButtonUp(const CFX_PointF& point, uint32_t nFlag) {
264  CPWL_Wnd::OnLButtonUp(point, nFlag);
265
266  if (m_bMouseDown) {
267    ReleaseCapture();
268    m_bMouseDown = false;
269  }
270
271  bool bExit = false;
272  OnNotifySelChanged(false, bExit, nFlag);
273
274  return true;
275}
276
277void CPWL_ListBox::SetHoverSel(bool bHoverSel) {
278  m_bHoverSel = bHoverSel;
279}
280
281bool CPWL_ListBox::OnMouseMove(const CFX_PointF& point, uint32_t nFlag) {
282  CPWL_Wnd::OnMouseMove(point, nFlag);
283
284  if (m_bHoverSel && !IsCaptureMouse() && ClientHitTest(point))
285    m_pList->Select(m_pList->GetItemIndex(point));
286  if (m_bMouseDown)
287    m_pList->OnMouseMove(point, IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
288
289  return true;
290}
291
292void CPWL_ListBox::OnNotify(CPWL_Wnd* pWnd,
293                            uint32_t msg,
294                            intptr_t wParam,
295                            intptr_t lParam) {
296  CPWL_Wnd::OnNotify(pWnd, msg, wParam, lParam);
297
298  FX_FLOAT fPos;
299
300  switch (msg) {
301    case PNM_SETSCROLLINFO:
302      switch (wParam) {
303        case SBT_VSCROLL:
304          if (CPWL_Wnd* pChild = GetVScrollBar()) {
305            pChild->OnNotify(pWnd, PNM_SETSCROLLINFO, wParam, lParam);
306          }
307          break;
308      }
309      break;
310    case PNM_SETSCROLLPOS:
311      switch (wParam) {
312        case SBT_VSCROLL:
313          if (CPWL_Wnd* pChild = GetVScrollBar()) {
314            pChild->OnNotify(pWnd, PNM_SETSCROLLPOS, wParam, lParam);
315          }
316          break;
317      }
318      break;
319    case PNM_SCROLLWINDOW:
320      fPos = *(FX_FLOAT*)lParam;
321      switch (wParam) {
322        case SBT_VSCROLL:
323          m_pList->SetScrollPos(CFX_PointF(0, fPos));
324          break;
325      }
326      break;
327  }
328}
329
330void CPWL_ListBox::KillFocus() {
331  CPWL_Wnd::KillFocus();
332}
333
334void CPWL_ListBox::RePosChildWnd() {
335  CPWL_Wnd::RePosChildWnd();
336
337  m_pList->SetPlateRect(GetListRect());
338}
339
340void CPWL_ListBox::OnNotifySelChanged(bool bKeyDown,
341                                      bool& bExit,
342                                      uint32_t nFlag) {
343  if (!m_pFillerNotify)
344    return;
345
346  bool bRC = true;
347  CFX_WideString swChange = GetText();
348  CFX_WideString strChangeEx;
349  int nSelStart = 0;
350  int nSelEnd = swChange.GetLength();
351  m_pFillerNotify->OnBeforeKeyStroke(GetAttachedData(), swChange, strChangeEx,
352                                     nSelStart, nSelEnd, bKeyDown, bRC, bExit,
353                                     nFlag);
354}
355
356CFX_FloatRect CPWL_ListBox::GetFocusRect() const {
357  if (m_pList->IsMultipleSel()) {
358    CFX_FloatRect rcCaret = m_pList->GetItemRect(m_pList->GetCaret());
359    rcCaret.Intersect(GetClientRect());
360    return rcCaret;
361  }
362
363  return CPWL_Wnd::GetFocusRect();
364}
365
366void CPWL_ListBox::AddString(const CFX_WideString& str) {
367  m_pList->AddString(str);
368}
369
370CFX_WideString CPWL_ListBox::GetText() const {
371  return m_pList->GetText();
372}
373
374void CPWL_ListBox::SetFontSize(FX_FLOAT fFontSize) {
375  m_pList->SetFontSize(fFontSize);
376}
377
378FX_FLOAT CPWL_ListBox::GetFontSize() const {
379  return m_pList->GetFontSize();
380}
381
382void CPWL_ListBox::Select(int32_t nItemIndex) {
383  m_pList->Select(nItemIndex);
384}
385
386void CPWL_ListBox::SetCaret(int32_t nItemIndex) {
387  m_pList->SetCaret(nItemIndex);
388}
389
390void CPWL_ListBox::SetTopVisibleIndex(int32_t nItemIndex) {
391  m_pList->SetTopItem(nItemIndex);
392}
393
394void CPWL_ListBox::ScrollToListItem(int32_t nItemIndex) {
395  m_pList->ScrollToListItem(nItemIndex);
396}
397
398void CPWL_ListBox::ResetContent() {
399  m_pList->Empty();
400}
401
402void CPWL_ListBox::Reset() {
403  m_pList->Cancel();
404}
405
406bool CPWL_ListBox::IsMultipleSel() const {
407  return m_pList->IsMultipleSel();
408}
409
410int32_t CPWL_ListBox::GetCaretIndex() const {
411  return m_pList->GetCaret();
412}
413
414int32_t CPWL_ListBox::GetCurSel() const {
415  return m_pList->GetSelect();
416}
417
418bool CPWL_ListBox::IsItemSelected(int32_t nItemIndex) const {
419  return m_pList->IsItemSelected(nItemIndex);
420}
421
422int32_t CPWL_ListBox::GetTopVisibleIndex() const {
423  m_pList->ScrollToListItem(m_pList->GetFirstSelected());
424  return m_pList->GetTopItem();
425}
426
427int32_t CPWL_ListBox::GetCount() const {
428  return m_pList->GetCount();
429}
430
431int32_t CPWL_ListBox::FindNext(int32_t nIndex, FX_WCHAR nChar) const {
432  return m_pList->FindNext(nIndex, nChar);
433}
434
435CFX_FloatRect CPWL_ListBox::GetContentRect() const {
436  return m_pList->GetContentRect();
437}
438
439FX_FLOAT CPWL_ListBox::GetFirstHeight() const {
440  return m_pList->GetFirstHeight();
441}
442
443CFX_FloatRect CPWL_ListBox::GetListRect() const {
444  return CPWL_Utils::DeflateRect(
445      GetWindowRect(), (FX_FLOAT)(GetBorderWidth() + GetInnerBorderWidth()));
446}
447
448bool CPWL_ListBox::OnMouseWheel(short zDelta,
449                                const CFX_PointF& point,
450                                uint32_t nFlag) {
451  if (zDelta < 0)
452    m_pList->OnVK_DOWN(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
453  else
454    m_pList->OnVK_UP(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
455
456  bool bExit = false;
457  OnNotifySelChanged(false, bExit, nFlag);
458  return true;
459}
460