CPDFDoc_Environment::GetAnnotHandlerMgr() never returns nullptr.
[pdfium.git] / fpdfsdk / src / pdfwindow / PWL_ComboBox.cpp
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 "../../include/pdfwindow/PDFWindow.h"
8 #include "../../include/pdfwindow/PWL_Wnd.h"
9 #include "../../include/pdfwindow/PWL_EditCtrl.h"
10 #include "../../include/pdfwindow/PWL_Edit.h"
11 #include "../../include/pdfwindow/PWL_ListBox.h"
12 #include "../../include/pdfwindow/PWL_ComboBox.h"
13 #include "../../include/pdfwindow/PWL_Utils.h"
14
15 #define PWLCB_DEFAULTFONTSIZE 12.0f
16
17 #define IsFloatZero(f) ((f) < 0.0001 && (f) > -0.0001)
18 #define IsFloatBigger(fa, fb) ((fa) > (fb) && !IsFloatZero((fa) - (fb)))
19 #define IsFloatSmaller(fa, fb) ((fa) < (fb) && !IsFloatZero((fa) - (fb)))
20 #define IsFloatEqual(fa, fb) IsFloatZero((fa) - (fb))
21
22 /* ---------------------------- CPWL_CBListBox ---------------------------- */
23
24 FX_BOOL CPWL_CBListBox::OnLButtonUp(const CPDF_Point& point, FX_DWORD nFlag) {
25   CPWL_Wnd::OnLButtonUp(point, nFlag);
26
27   if (m_bMouseDown) {
28     ReleaseCapture();
29     m_bMouseDown = FALSE;
30
31     if (ClientHitTest(point)) {
32       if (CPWL_Wnd* pParent = GetParentWindow()) {
33         pParent->OnNotify(this, PNM_LBUTTONUP, 0,
34                           PWL_MAKEDWORD(point.x, point.y));
35       }
36
37       FX_BOOL bExit = FALSE;
38       OnNotifySelChanged(FALSE, bExit, nFlag);
39       if (bExit)
40         return FALSE;
41     }
42   }
43
44   return TRUE;
45 }
46
47 FX_BOOL CPWL_CBListBox::OnKeyDownWithExit(FX_WORD nChar,
48                                           FX_BOOL& bExit,
49                                           FX_DWORD nFlag) {
50   if (!m_pList)
51     return FALSE;
52
53   switch (nChar) {
54     default:
55       return FALSE;
56     case FWL_VKEY_Up:
57     case FWL_VKEY_Down:
58     case FWL_VKEY_Home:
59     case FWL_VKEY_Left:
60     case FWL_VKEY_End:
61     case FWL_VKEY_Right:
62       break;
63   }
64
65   switch (nChar) {
66     case FWL_VKEY_Up:
67       m_pList->OnVK_UP(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
68       break;
69     case FWL_VKEY_Down:
70       m_pList->OnVK_DOWN(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
71       break;
72     case FWL_VKEY_Home:
73       m_pList->OnVK_HOME(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
74       break;
75     case FWL_VKEY_Left:
76       m_pList->OnVK_LEFT(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
77       break;
78     case FWL_VKEY_End:
79       m_pList->OnVK_END(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
80       break;
81     case FWL_VKEY_Right:
82       m_pList->OnVK_RIGHT(IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag));
83       break;
84     case FWL_VKEY_Delete:
85       break;
86   }
87
88   OnNotifySelChanged(TRUE, bExit, nFlag);
89
90   return TRUE;
91 }
92
93 FX_BOOL CPWL_CBListBox::OnCharWithExit(FX_WORD nChar,
94                                        FX_BOOL& bExit,
95                                        FX_DWORD nFlag) {
96   if (!m_pList)
97     return FALSE;
98
99   if (!m_pList->OnChar(nChar, IsSHIFTpressed(nFlag), IsCTRLpressed(nFlag)))
100     return FALSE;
101
102   if (CPWL_ComboBox* pComboBox = (CPWL_ComboBox*)GetParentWindow()) {
103     pComboBox->SetSelectText();
104   }
105
106   OnNotifySelChanged(TRUE, bExit, nFlag);
107
108   return TRUE;
109 }
110
111 /* ---------------------------- CPWL_CBButton ---------------------------- */
112
113 void CPWL_CBButton::GetThisAppearanceStream(CFX_ByteTextBuf& sAppStream) {
114   CPWL_Wnd::GetThisAppearanceStream(sAppStream);
115
116   CPDF_Rect rectWnd = CPWL_Wnd::GetWindowRect();
117
118   if (IsVisible() && !rectWnd.IsEmpty()) {
119     CFX_ByteTextBuf sButton;
120
121     CPDF_Point ptCenter = GetCenterPoint();
122
123     CPDF_Point pt1(ptCenter.x - PWL_CBBUTTON_TRIANGLE_HALFLEN,
124                    ptCenter.y + PWL_CBBUTTON_TRIANGLE_HALFLEN * 0.5f);
125     CPDF_Point pt2(ptCenter.x + PWL_CBBUTTON_TRIANGLE_HALFLEN,
126                    ptCenter.y + PWL_CBBUTTON_TRIANGLE_HALFLEN * 0.5f);
127     CPDF_Point pt3(ptCenter.x,
128                    ptCenter.y - PWL_CBBUTTON_TRIANGLE_HALFLEN * 0.5f);
129
130     if (IsFloatBigger(rectWnd.right - rectWnd.left,
131                       PWL_CBBUTTON_TRIANGLE_HALFLEN * 2) &&
132         IsFloatBigger(rectWnd.top - rectWnd.bottom,
133                       PWL_CBBUTTON_TRIANGLE_HALFLEN)) {
134       sButton << "0 g\n";
135       sButton << pt1.x << " " << pt1.y << " m\n";
136       sButton << pt2.x << " " << pt2.y << " l\n";
137       sButton << pt3.x << " " << pt3.y << " l\n";
138       sButton << pt1.x << " " << pt1.y << " l f\n";
139
140       sAppStream << "q\n" << sButton << "Q\n";
141     }
142   }
143 }
144
145 void CPWL_CBButton::DrawThisAppearance(CFX_RenderDevice* pDevice,
146                                        CPDF_Matrix* pUser2Device) {
147   CPWL_Wnd::DrawThisAppearance(pDevice, pUser2Device);
148
149   CPDF_Rect rectWnd = CPWL_Wnd::GetWindowRect();
150
151   if (IsVisible() && !rectWnd.IsEmpty()) {
152     CPDF_Point ptCenter = GetCenterPoint();
153
154     CPDF_Point pt1(ptCenter.x - PWL_CBBUTTON_TRIANGLE_HALFLEN,
155                    ptCenter.y + PWL_CBBUTTON_TRIANGLE_HALFLEN * 0.5f);
156     CPDF_Point pt2(ptCenter.x + PWL_CBBUTTON_TRIANGLE_HALFLEN,
157                    ptCenter.y + PWL_CBBUTTON_TRIANGLE_HALFLEN * 0.5f);
158     CPDF_Point pt3(ptCenter.x,
159                    ptCenter.y - PWL_CBBUTTON_TRIANGLE_HALFLEN * 0.5f);
160
161     if (IsFloatBigger(rectWnd.right - rectWnd.left,
162                       PWL_CBBUTTON_TRIANGLE_HALFLEN * 2) &&
163         IsFloatBigger(rectWnd.top - rectWnd.bottom,
164                       PWL_CBBUTTON_TRIANGLE_HALFLEN)) {
165       CFX_PathData path;
166
167       path.SetPointCount(4);
168       path.SetPoint(0, pt1.x, pt1.y, FXPT_MOVETO);
169       path.SetPoint(1, pt2.x, pt2.y, FXPT_LINETO);
170       path.SetPoint(2, pt3.x, pt3.y, FXPT_LINETO);
171       path.SetPoint(3, pt1.x, pt1.y, FXPT_LINETO);
172
173       pDevice->DrawPath(&path, pUser2Device, NULL,
174                         CPWL_Utils::PWLColorToFXColor(PWL_DEFAULT_BLACKCOLOR,
175                                                       GetTransparency()),
176                         0, FXFILL_ALTERNATE);
177     }
178   }
179 }
180
181 FX_BOOL CPWL_CBButton::OnLButtonDown(const CPDF_Point& point, FX_DWORD nFlag) {
182   CPWL_Wnd::OnLButtonDown(point, nFlag);
183
184   SetCapture();
185
186   if (CPWL_Wnd* pParent = GetParentWindow()) {
187     pParent->OnNotify(this, PNM_LBUTTONDOWN, 0,
188                       PWL_MAKEDWORD(point.x, point.y));
189   }
190
191   return TRUE;
192 }
193
194 FX_BOOL CPWL_CBButton::OnLButtonUp(const CPDF_Point& point, FX_DWORD nFlag) {
195   CPWL_Wnd::OnLButtonUp(point, nFlag);
196
197   ReleaseCapture();
198
199   return TRUE;
200 }
201
202 /* ---------------------------- CPWL_ComboBox ---------------------------- */
203
204 CPWL_ComboBox::CPWL_ComboBox()
205     : m_pEdit(NULL),
206       m_pButton(NULL),
207       m_pList(NULL),
208       m_bPopup(FALSE),
209       m_nPopupWhere(0),
210       m_nSelectItem(-1),
211       m_pFillerNotify(NULL) {}
212
213 CFX_ByteString CPWL_ComboBox::GetClassName() const {
214   return "CPWL_ComboBox";
215 }
216
217 void CPWL_ComboBox::OnCreate(PWL_CREATEPARAM& cp) {
218   cp.dwFlags &= ~PWS_HSCROLL;
219   cp.dwFlags &= ~PWS_VSCROLL;
220 }
221
222 void CPWL_ComboBox::SetFocus() {
223   if (m_pEdit)
224     m_pEdit->SetFocus();
225 }
226
227 void CPWL_ComboBox::KillFocus() {
228   SetPopup(FALSE);
229   CPWL_Wnd::KillFocus();
230 }
231
232 CFX_WideString CPWL_ComboBox::GetText() const {
233   if (m_pEdit) {
234     return m_pEdit->GetText();
235   }
236   return CFX_WideString();
237 }
238
239 void CPWL_ComboBox::SetText(const FX_WCHAR* text) {
240   if (m_pEdit)
241     m_pEdit->SetText(text);
242 }
243
244 void CPWL_ComboBox::AddString(const FX_WCHAR* string) {
245   if (m_pList)
246     m_pList->AddString(string);
247 }
248
249 int32_t CPWL_ComboBox::GetSelect() const {
250   return m_nSelectItem;
251 }
252
253 void CPWL_ComboBox::SetSelect(int32_t nItemIndex) {
254   if (m_pList)
255     m_pList->Select(nItemIndex);
256
257   m_pEdit->SetText(m_pList->GetText().c_str());
258
259   m_nSelectItem = nItemIndex;
260 }
261
262 void CPWL_ComboBox::SetEditSel(int32_t nStartChar, int32_t nEndChar) {
263   if (m_pEdit) {
264     m_pEdit->SetSel(nStartChar, nEndChar);
265   }
266 }
267
268 void CPWL_ComboBox::GetEditSel(int32_t& nStartChar, int32_t& nEndChar) const {
269   nStartChar = -1;
270   nEndChar = -1;
271
272   if (m_pEdit) {
273     m_pEdit->GetSel(nStartChar, nEndChar);
274   }
275 }
276
277 void CPWL_ComboBox::Clear() {
278   if (m_pEdit) {
279     m_pEdit->Clear();
280   }
281 }
282
283 void CPWL_ComboBox::CreateChildWnd(const PWL_CREATEPARAM& cp) {
284   CreateEdit(cp);
285   CreateButton(cp);
286   CreateListBox(cp);
287 }
288
289 void CPWL_ComboBox::CreateEdit(const PWL_CREATEPARAM& cp) {
290   if (!m_pEdit) {
291     m_pEdit = new CPWL_CBEdit;
292     m_pEdit->AttachFFLData(m_pFormFiller);
293
294     PWL_CREATEPARAM ecp = cp;
295     ecp.pParentWnd = this;
296     ecp.dwFlags = PWS_VISIBLE | PWS_CHILD | PWS_BORDER | PES_CENTER |
297                   PES_AUTOSCROLL | PES_UNDO;
298
299     if (HasFlag(PWS_AUTOFONTSIZE))
300       ecp.dwFlags |= PWS_AUTOFONTSIZE;
301
302     if (!HasFlag(PCBS_ALLOWCUSTOMTEXT))
303       ecp.dwFlags |= PWS_READONLY;
304
305     ecp.rcRectWnd = CPDF_Rect(0, 0, 0, 0);
306     ecp.dwBorderWidth = 0;
307     ecp.nBorderStyle = PBS_SOLID;
308
309     m_pEdit->Create(ecp);
310   }
311 }
312
313 void CPWL_ComboBox::CreateButton(const PWL_CREATEPARAM& cp) {
314   if (!m_pButton) {
315     m_pButton = new CPWL_CBButton;
316
317     PWL_CREATEPARAM bcp = cp;
318     bcp.pParentWnd = this;
319     bcp.dwFlags = PWS_VISIBLE | PWS_CHILD | PWS_BORDER | PWS_BACKGROUND;
320     bcp.sBackgroundColor = PWL_SCROLLBAR_BKCOLOR;
321     bcp.sBorderColor = PWL_DEFAULT_BLACKCOLOR;
322     bcp.dwBorderWidth = 2;
323     bcp.nBorderStyle = PBS_BEVELED;
324     bcp.eCursorType = FXCT_ARROW;
325
326     m_pButton->Create(bcp);
327   }
328 }
329
330 void CPWL_ComboBox::CreateListBox(const PWL_CREATEPARAM& cp) {
331   if (!m_pList) {
332     m_pList = new CPWL_CBListBox;
333     m_pList->AttachFFLData(m_pFormFiller);
334     PWL_CREATEPARAM lcp = cp;
335     lcp.pParentWnd = this;
336     lcp.dwFlags =
337         PWS_CHILD | PWS_BORDER | PWS_BACKGROUND | PLBS_HOVERSEL | PWS_VSCROLL;
338     lcp.nBorderStyle = PBS_SOLID;
339     lcp.dwBorderWidth = 1;
340     lcp.eCursorType = FXCT_ARROW;
341     lcp.rcRectWnd = CPDF_Rect(0, 0, 0, 0);
342
343     if (cp.dwFlags & PWS_AUTOFONTSIZE)
344       lcp.fFontSize = PWLCB_DEFAULTFONTSIZE;
345     else
346       lcp.fFontSize = cp.fFontSize;
347
348     if (cp.sBorderColor.nColorType == COLORTYPE_TRANSPARENT)
349       lcp.sBorderColor = PWL_DEFAULT_BLACKCOLOR;
350
351     if (cp.sBackgroundColor.nColorType == COLORTYPE_TRANSPARENT)
352       lcp.sBackgroundColor = PWL_DEFAULT_WHITECOLOR;
353
354     m_pList->Create(lcp);
355   }
356 }
357
358 void CPWL_ComboBox::RePosChildWnd() {
359   CPDF_Rect rcClient = GetClientRect();
360
361   if (m_bPopup) {
362     CPDF_Rect rclient = GetClientRect();
363     CPDF_Rect rcButton = rclient;
364     CPDF_Rect rcEdit = rcClient;
365     CPDF_Rect rcList = CPWL_Wnd::GetWindowRect();
366
367     FX_FLOAT fOldWindowHeight = m_rcOldWindow.Height();
368     FX_FLOAT fOldClientHeight = fOldWindowHeight - GetBorderWidth() * 2;
369
370     switch (m_nPopupWhere) {
371       case 0:
372         rcButton.left = rcButton.right - PWL_COMBOBOX_BUTTON_WIDTH;
373
374         if (rcButton.left < rclient.left)
375           rcButton.left = rclient.left;
376
377         rcButton.bottom = rcButton.top - fOldClientHeight;
378
379         rcEdit.right = rcButton.left - 1.0f;
380
381         if (rcEdit.left < rclient.left)
382           rcEdit.left = rclient.left;
383
384         if (rcEdit.right < rcEdit.left)
385           rcEdit.right = rcEdit.left;
386
387         rcEdit.bottom = rcEdit.top - fOldClientHeight;
388
389         rcList.top -= fOldWindowHeight;
390
391         break;
392       case 1:
393         rcButton.left = rcButton.right - PWL_COMBOBOX_BUTTON_WIDTH;
394
395         if (rcButton.left < rclient.left)
396           rcButton.left = rclient.left;
397
398         rcButton.top = rcButton.bottom + fOldClientHeight;
399
400         rcEdit.right = rcButton.left - 1.0f;
401
402         if (rcEdit.left < rclient.left)
403           rcEdit.left = rclient.left;
404
405         if (rcEdit.right < rcEdit.left)
406           rcEdit.right = rcEdit.left;
407
408         rcEdit.top = rcEdit.bottom + fOldClientHeight;
409
410         rcList.bottom += fOldWindowHeight;
411
412         break;
413     }
414
415     if (m_pButton)
416       m_pButton->Move(rcButton, TRUE, FALSE);
417
418     if (m_pEdit)
419       m_pEdit->Move(rcEdit, TRUE, FALSE);
420
421     if (m_pList) {
422       m_pList->SetVisible(TRUE);
423       m_pList->Move(rcList, TRUE, FALSE);
424       m_pList->ScrollToListItem(m_nSelectItem);
425     }
426   } else {
427     CPDF_Rect rcButton = rcClient;
428
429     rcButton.left = rcButton.right - PWL_COMBOBOX_BUTTON_WIDTH;
430
431     if (rcButton.left < rcClient.left)
432       rcButton.left = rcClient.left;
433
434     if (m_pButton)
435       m_pButton->Move(rcButton, TRUE, FALSE);
436
437     CPDF_Rect rcEdit = rcClient;
438     rcEdit.right = rcButton.left - 1.0f;
439
440     if (rcEdit.left < rcClient.left)
441       rcEdit.left = rcClient.left;
442
443     if (rcEdit.right < rcEdit.left)
444       rcEdit.right = rcEdit.left;
445
446     if (m_pEdit)
447       m_pEdit->Move(rcEdit, TRUE, FALSE);
448
449     if (m_pList)
450       m_pList->SetVisible(FALSE);
451   }
452 }
453
454 void CPWL_ComboBox::SelectAll() {
455   if (m_pEdit && HasFlag(PCBS_ALLOWCUSTOMTEXT))
456     m_pEdit->SelectAll();
457 }
458
459 CPDF_Rect CPWL_ComboBox::GetFocusRect() const {
460   return CPDF_Rect();
461 }
462
463 void CPWL_ComboBox::SetPopup(FX_BOOL bPopup) {
464   if (!m_pList)
465     return;
466   if (bPopup == m_bPopup)
467     return;
468   FX_FLOAT fListHeight = m_pList->GetContentRect().Height();
469   if (!IsFloatBigger(fListHeight, 0.0f))
470     return;
471
472   if (bPopup) {
473     if (m_pFillerNotify) {
474       int32_t nWhere = 0;
475       FX_FLOAT fPopupRet = 0.0f;
476       FX_FLOAT fPopupMin = 0.0f;
477       if (m_pList->GetCount() > 3)
478         fPopupMin =
479             m_pList->GetFirstHeight() * 3 + m_pList->GetBorderWidth() * 2;
480       FX_FLOAT fPopupMax = fListHeight + m_pList->GetBorderWidth() * 2;
481       m_pFillerNotify->QueryWherePopup(GetAttachedData(), fPopupMin, fPopupMax,
482                                        nWhere, fPopupRet);
483
484       if (IsFloatBigger(fPopupRet, 0.0f)) {
485         m_bPopup = bPopup;
486
487         CPDF_Rect rcWindow = CPWL_Wnd::GetWindowRect();
488         m_rcOldWindow = rcWindow;
489         switch (nWhere) {
490           default:
491           case 0:
492             rcWindow.bottom -= fPopupRet;
493             break;
494           case 1:
495             rcWindow.top += fPopupRet;
496             break;
497         }
498
499         m_nPopupWhere = nWhere;
500         Move(rcWindow, TRUE, TRUE);
501       }
502     }
503   } else {
504     m_bPopup = bPopup;
505     Move(m_rcOldWindow, TRUE, TRUE);
506   }
507 }
508
509 FX_BOOL CPWL_ComboBox::OnKeyDown(FX_WORD nChar, FX_DWORD nFlag) {
510   if (!m_pList)
511     return FALSE;
512   if (!m_pEdit)
513     return FALSE;
514
515   m_nSelectItem = -1;
516
517   switch (nChar) {
518     case FWL_VKEY_Up:
519       if (m_pList->GetCurSel() > 0) {
520         FX_BOOL bExit = FALSE;
521         if (m_pList->OnKeyDownWithExit(nChar, bExit, nFlag)) {
522           if (bExit)
523             return FALSE;
524           SetSelectText();
525         }
526       }
527       return TRUE;
528     case FWL_VKEY_Down:
529       if (m_pList->GetCurSel() < m_pList->GetCount() - 1) {
530         FX_BOOL bExit = FALSE;
531         if (m_pList->OnKeyDownWithExit(nChar, bExit, nFlag)) {
532           if (bExit)
533             return FALSE;
534           SetSelectText();
535         }
536       }
537       return TRUE;
538   }
539
540   if (HasFlag(PCBS_ALLOWCUSTOMTEXT))
541     return m_pEdit->OnKeyDown(nChar, nFlag);
542
543   return FALSE;
544 }
545
546 FX_BOOL CPWL_ComboBox::OnChar(FX_WORD nChar, FX_DWORD nFlag) {
547   if (!m_pList)
548     return FALSE;
549
550   if (!m_pEdit)
551     return FALSE;
552
553   m_nSelectItem = -1;
554   if (HasFlag(PCBS_ALLOWCUSTOMTEXT))
555     return m_pEdit->OnChar(nChar, nFlag);
556
557   FX_BOOL bExit = FALSE;
558   return m_pList->OnCharWithExit(nChar, bExit, nFlag) ? bExit : FALSE;
559 }
560
561 void CPWL_ComboBox::OnNotify(CPWL_Wnd* pWnd,
562                              FX_DWORD msg,
563                              intptr_t wParam,
564                              intptr_t lParam) {
565   switch (msg) {
566     case PNM_LBUTTONDOWN:
567       if (pWnd == m_pButton) {
568         SetPopup(!m_bPopup);
569         return;
570       }
571       break;
572     case PNM_LBUTTONUP:
573       if (m_pEdit && m_pList) {
574         if (pWnd == m_pList) {
575           SetSelectText();
576           SelectAll();
577           m_pEdit->SetFocus();
578           SetPopup(FALSE);
579           return;
580         }
581       }
582   }
583
584   CPWL_Wnd::OnNotify(pWnd, msg, wParam, lParam);
585 }
586
587 FX_BOOL CPWL_ComboBox::IsPopup() const {
588   return m_bPopup;
589 }
590
591 void CPWL_ComboBox::SetSelectText() {
592   CFX_WideString swText = m_pList->GetText();
593   m_pEdit->SelectAll();
594   m_pEdit->ReplaceSel(m_pList->GetText().c_str());
595   m_pEdit->SelectAll();
596
597   m_nSelectItem = m_pList->GetCurSel();
598 }
599
600 FX_BOOL CPWL_ComboBox::IsModified() const {
601   return m_pEdit->IsModified();
602 }
603
604 void CPWL_ComboBox::SetFillerNotify(IPWL_Filler_Notify* pNotify) {
605   m_pFillerNotify = pNotify;
606
607   if (m_pEdit)
608     m_pEdit->SetFillerNotify(pNotify);
609
610   if (m_pList)
611     m_pList->SetFillerNotify(pNotify);
612 }