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