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