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