| // Copyright 2014 The PDFium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com | 
 |  | 
 | #include "xfa/fxfa/cxfa_ffcheckbutton.h" | 
 |  | 
 | #include "core/fxcrt/check.h" | 
 | #include "v8/include/cppgc/visitor.h" | 
 | #include "xfa/fwl/cfwl_checkbox.h" | 
 | #include "xfa/fwl/cfwl_messagemouse.h" | 
 | #include "xfa/fwl/cfwl_notedriver.h" | 
 | #include "xfa/fwl/cfwl_widgetmgr.h" | 
 | #include "xfa/fxfa/cxfa_ffapp.h" | 
 | #include "xfa/fxfa/cxfa_ffdoc.h" | 
 | #include "xfa/fxfa/cxfa_ffdocview.h" | 
 | #include "xfa/fxfa/cxfa_ffexclgroup.h" | 
 | #include "xfa/fxfa/cxfa_fffield.h" | 
 | #include "xfa/fxfa/cxfa_ffpageview.h" | 
 | #include "xfa/fxfa/cxfa_ffwidget.h" | 
 | #include "xfa/fxfa/parser/cxfa_border.h" | 
 | #include "xfa/fxfa/parser/cxfa_caption.h" | 
 | #include "xfa/fxfa/parser/cxfa_checkbutton.h" | 
 | #include "xfa/fxfa/parser/cxfa_para.h" | 
 |  | 
 | CXFA_FFCheckButton::CXFA_FFCheckButton(CXFA_Node* pNode, | 
 |                                        CXFA_CheckButton* button) | 
 |     : CXFA_FFField(pNode), button_(button) {} | 
 |  | 
 | CXFA_FFCheckButton::~CXFA_FFCheckButton() = default; | 
 |  | 
 | void CXFA_FFCheckButton::Trace(cppgc::Visitor* visitor) const { | 
 |   CXFA_FFField::Trace(visitor); | 
 |   visitor->Trace(old_delegate_); | 
 |   visitor->Trace(button_); | 
 | } | 
 |  | 
 | bool CXFA_FFCheckButton::LoadWidget() { | 
 |   DCHECK(!IsLoaded()); | 
 |  | 
 |   CFWL_CheckBox* pCheckBox = cppgc::MakeGarbageCollected<CFWL_CheckBox>( | 
 |       GetFWLApp()->GetHeap()->GetAllocationHandle(), GetFWLApp()); | 
 |   SetNormalWidget(pCheckBox); | 
 |   pCheckBox->SetAdapterIface(this); | 
 |  | 
 |   CFWL_NoteDriver* pNoteDriver = pCheckBox->GetFWLApp()->GetNoteDriver(); | 
 |   pNoteDriver->RegisterEventTarget(pCheckBox, pCheckBox); | 
 |   old_delegate_ = pCheckBox->GetDelegate(); | 
 |   pCheckBox->SetDelegate(this); | 
 |   if (node_->IsRadioButton()) { | 
 |     pCheckBox->ModifyStyleExts(FWL_STYLEEXT_CKB_RadioButton, 0xFFFFFFFF); | 
 |   } | 
 |  | 
 |   { | 
 |     CFWL_Widget::ScopedUpdateLock update_lock(pCheckBox); | 
 |     UpdateWidgetProperty(); | 
 |     SetFWLCheckState(node_->GetCheckState()); | 
 |   } | 
 |  | 
 |   return CXFA_FFField::LoadWidget(); | 
 | } | 
 |  | 
 | void CXFA_FFCheckButton::UpdateWidgetProperty() { | 
 |   auto* pCheckBox = static_cast<CFWL_CheckBox*>(GetNormalWidget()); | 
 |   if (!pCheckBox) { | 
 |     return; | 
 |   } | 
 |  | 
 |   pCheckBox->SetBoxSize(node_->GetCheckButtonSize()); | 
 |   uint32_t dwStyleEx = FWL_STYLEEXT_CKB_SignShapeCross; | 
 |   switch (button_->GetMark()) { | 
 |     case XFA_AttributeValue::Check: | 
 |       dwStyleEx = FWL_STYLEEXT_CKB_SignShapeCheck; | 
 |       break; | 
 |     case XFA_AttributeValue::Circle: | 
 |       dwStyleEx = FWL_STYLEEXT_CKB_SignShapeCircle; | 
 |       break; | 
 |     case XFA_AttributeValue::Cross: | 
 |       break; | 
 |     case XFA_AttributeValue::Diamond: | 
 |       dwStyleEx = FWL_STYLEEXT_CKB_SignShapeDiamond; | 
 |       break; | 
 |     case XFA_AttributeValue::Square: | 
 |       dwStyleEx = FWL_STYLEEXT_CKB_SignShapeSquare; | 
 |       break; | 
 |     case XFA_AttributeValue::Star: | 
 |       dwStyleEx = FWL_STYLEEXT_CKB_SignShapeStar; | 
 |       break; | 
 |     default: { | 
 |       if (button_->IsRound()) { | 
 |         dwStyleEx = FWL_STYLEEXT_CKB_SignShapeCircle; | 
 |       } | 
 |     } break; | 
 |   } | 
 |   if (button_->IsAllowNeutral()) { | 
 |     dwStyleEx |= FWL_STYLEEXT_CKB_3State; | 
 |   } | 
 |  | 
 |   pCheckBox->ModifyStyleExts( | 
 |       dwStyleEx, FWL_STYLEEXT_CKB_SignShapeMask | FWL_STYLEEXT_CKB_3State); | 
 | } | 
 |  | 
 | void CXFA_FFCheckButton::PerformLayout() { | 
 |   CXFA_FFWidget::PerformLayout(); | 
 |  | 
 |   float fCheckSize = node_->GetCheckButtonSize(); | 
 |   CXFA_Margin* margin = node_->GetMarginIfExists(); | 
 |   CFX_RectF rtWidget = GetRectWithoutRotate(); | 
 |   XFA_RectWithoutMargin(&rtWidget, margin); | 
 |  | 
 |   XFA_AttributeValue iCapPlacement = XFA_AttributeValue::Unknown; | 
 |   float fCapReserve = 0; | 
 |   CXFA_Caption* caption = node_->GetCaptionIfExists(); | 
 |   if (caption && caption->IsVisible()) { | 
 |     caption_rect_ = rtWidget; | 
 |     iCapPlacement = caption->GetPlacementType(); | 
 |     fCapReserve = caption->GetReserve(); | 
 |     if (fCapReserve <= 0) { | 
 |       if (iCapPlacement == XFA_AttributeValue::Top || | 
 |           iCapPlacement == XFA_AttributeValue::Bottom) { | 
 |         fCapReserve = rtWidget.height - fCheckSize; | 
 |       } else { | 
 |         fCapReserve = rtWidget.width - fCheckSize; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   XFA_AttributeValue iHorzAlign = XFA_AttributeValue::Left; | 
 |   XFA_AttributeValue iVertAlign = XFA_AttributeValue::Top; | 
 |   CXFA_Para* para = node_->GetParaIfExists(); | 
 |   if (para) { | 
 |     iHorzAlign = para->GetHorizontalAlign(); | 
 |     iVertAlign = para->GetVerticalAlign(); | 
 |   } | 
 |  | 
 |   uirect_ = rtWidget; | 
 |   CXFA_Margin* captionMargin = caption ? caption->GetMarginIfExists() : nullptr; | 
 |   switch (iCapPlacement) { | 
 |     case XFA_AttributeValue::Left: { | 
 |       caption_rect_.width = fCapReserve; | 
 |       CapLeftRightPlacement(captionMargin); | 
 |       uirect_.width -= fCapReserve; | 
 |       uirect_.left += fCapReserve; | 
 |       break; | 
 |     } | 
 |     case XFA_AttributeValue::Top: { | 
 |       caption_rect_.height = fCapReserve; | 
 |       XFA_RectWithoutMargin(&caption_rect_, captionMargin); | 
 |       uirect_.height -= fCapReserve; | 
 |       uirect_.top += fCapReserve; | 
 |       break; | 
 |     } | 
 |     case XFA_AttributeValue::Right: { | 
 |       caption_rect_.left = caption_rect_.right() - fCapReserve; | 
 |       caption_rect_.width = fCapReserve; | 
 |       CapLeftRightPlacement(captionMargin); | 
 |       uirect_.width -= fCapReserve; | 
 |       break; | 
 |     } | 
 |     case XFA_AttributeValue::Bottom: { | 
 |       caption_rect_.top = caption_rect_.bottom() - fCapReserve; | 
 |       caption_rect_.height = fCapReserve; | 
 |       XFA_RectWithoutMargin(&caption_rect_, captionMargin); | 
 |       uirect_.height -= fCapReserve; | 
 |       break; | 
 |     } | 
 |     case XFA_AttributeValue::Inline: | 
 |       break; | 
 |     default: | 
 |       iHorzAlign = XFA_AttributeValue::Right; | 
 |       break; | 
 |   } | 
 |  | 
 |   if (iHorzAlign == XFA_AttributeValue::Center) { | 
 |     uirect_.left += (uirect_.width - fCheckSize) / 2; | 
 |   } else if (iHorzAlign == XFA_AttributeValue::Right) { | 
 |     uirect_.left = uirect_.right() - fCheckSize; | 
 |   } | 
 |  | 
 |   if (iVertAlign == XFA_AttributeValue::Middle) { | 
 |     uirect_.top += (uirect_.height - fCheckSize) / 2; | 
 |   } else if (iVertAlign == XFA_AttributeValue::Bottom) { | 
 |     uirect_.top = uirect_.bottom() - fCheckSize; | 
 |   } | 
 |  | 
 |   uirect_.width = fCheckSize; | 
 |   uirect_.height = fCheckSize; | 
 |   AddUIMargin(iCapPlacement); | 
 |   check_box_rect_ = uirect_; | 
 |   CXFA_Border* borderUI = node_->GetUIBorder(); | 
 |   if (borderUI) { | 
 |     CXFA_Margin* borderMargin = borderUI->GetMarginIfExists(); | 
 |     XFA_RectWithoutMargin(&uirect_, borderMargin); | 
 |   } | 
 |  | 
 |   uirect_.Normalize(); | 
 |   LayoutCaption(); | 
 |   SetFWLRect(); | 
 |   if (GetNormalWidget()) { | 
 |     GetNormalWidget()->Update(); | 
 |   } | 
 | } | 
 |  | 
 | void CXFA_FFCheckButton::CapLeftRightPlacement( | 
 |     const CXFA_Margin* captionMargin) { | 
 |   XFA_RectWithoutMargin(&caption_rect_, captionMargin); | 
 |   if (caption_rect_.height < 0) { | 
 |     caption_rect_.top += caption_rect_.height; | 
 |   } | 
 |   if (caption_rect_.width < 0) { | 
 |     caption_rect_.left += caption_rect_.width; | 
 |     caption_rect_.width = -caption_rect_.width; | 
 |   } | 
 | } | 
 |  | 
 | void CXFA_FFCheckButton::AddUIMargin(XFA_AttributeValue iCapPlacement) { | 
 |   CFX_RectF rtUIMargin = node_->GetUIMargin(); | 
 |   uirect_.top -= rtUIMargin.top / 2 - rtUIMargin.height / 2; | 
 |  | 
 |   float fLeftAddRight = rtUIMargin.left + rtUIMargin.width; | 
 |   float fTopAddBottom = rtUIMargin.top + rtUIMargin.height; | 
 |   if (uirect_.width < fLeftAddRight) { | 
 |     if (iCapPlacement == XFA_AttributeValue::Right || | 
 |         iCapPlacement == XFA_AttributeValue::Left) { | 
 |       uirect_.left -= fLeftAddRight - uirect_.width; | 
 |     } else { | 
 |       uirect_.left -= 2 * (fLeftAddRight - uirect_.width); | 
 |     } | 
 |     uirect_.width += 2 * (fLeftAddRight - uirect_.width); | 
 |   } | 
 |   if (uirect_.height < fTopAddBottom) { | 
 |     if (iCapPlacement == XFA_AttributeValue::Right) { | 
 |       uirect_.left -= fTopAddBottom - uirect_.height; | 
 |     } | 
 |  | 
 |     uirect_.top -= fTopAddBottom - uirect_.height; | 
 |     uirect_.height += 2 * (fTopAddBottom - uirect_.height); | 
 |   } | 
 | } | 
 |  | 
 | void CXFA_FFCheckButton::RenderWidget(CFGAS_GEGraphics* pGS, | 
 |                                       const CFX_Matrix& matrix, | 
 |                                       HighlightOption highlight) { | 
 |   if (!HasVisibleStatus()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   CFX_Matrix mtRotate = GetRotateMatrix(); | 
 |   mtRotate.Concat(matrix); | 
 |  | 
 |   CXFA_FFWidget::RenderWidget(pGS, mtRotate, highlight); | 
 |   DrawBorderWithFlag(pGS, node_->GetUIBorder(), uirect_, mtRotate, | 
 |                      button_->IsRound()); | 
 |   RenderCaption(pGS, mtRotate); | 
 |   DrawHighlight(pGS, mtRotate, highlight, | 
 |                 button_->IsRound() ? kRoundShape : kSquareShape); | 
 |   CFX_Matrix mt(1, 0, 0, 1, check_box_rect_.left, check_box_rect_.top); | 
 |   mt.Concat(mtRotate); | 
 |   GetApp()->GetFWLWidgetMgr()->OnDrawWidget(GetNormalWidget(), pGS, mt); | 
 | } | 
 |  | 
 | bool CXFA_FFCheckButton::OnLButtonUp(Mask<XFA_FWL_KeyFlag> dwFlags, | 
 |                                      const CFX_PointF& point) { | 
 |   if (!GetNormalWidget() || !IsButtonDown()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   SetButtonDown(false); | 
 |   CFWL_MessageMouse msg(GetNormalWidget(), | 
 |                         CFWL_MessageMouse::MouseCommand::kLeftButtonUp, dwFlags, | 
 |                         FWLToClient(point)); | 
 |   SendMessageToFWLWidget(&msg); | 
 |   return true; | 
 | } | 
 |  | 
 | XFA_CheckState CXFA_FFCheckButton::FWLState2XFAState() { | 
 |   uint32_t dwState = GetNormalWidget()->GetStates(); | 
 |   if (dwState & FWL_STATE_CKB_Checked) { | 
 |     return XFA_CheckState::kOn; | 
 |   } | 
 |   if (dwState & FWL_STATE_CKB_Neutral) { | 
 |     return XFA_CheckState::kNeutral; | 
 |   } | 
 |   return XFA_CheckState::kOff; | 
 | } | 
 |  | 
 | bool CXFA_FFCheckButton::CommitData() { | 
 |   node_->SetCheckState(FWLState2XFAState()); | 
 |   return true; | 
 | } | 
 |  | 
 | bool CXFA_FFCheckButton::IsDataChanged() { | 
 |   XFA_CheckState eCheckState = FWLState2XFAState(); | 
 |   return node_->GetCheckState() != eCheckState; | 
 | } | 
 |  | 
 | void CXFA_FFCheckButton::SetFWLCheckState(XFA_CheckState eCheckState) { | 
 |   if (eCheckState == XFA_CheckState::kNeutral) { | 
 |     GetNormalWidget()->SetStates(FWL_STATE_CKB_Neutral); | 
 |   } else if (eCheckState == XFA_CheckState::kOn) { | 
 |     GetNormalWidget()->SetStates(FWL_STATE_CKB_Checked); | 
 |   } else { | 
 |     GetNormalWidget()->RemoveStates(FWL_STATE_CKB_Checked); | 
 |   } | 
 | } | 
 |  | 
 | void CXFA_FFCheckButton::UpdateFWLData() { | 
 |   if (!GetNormalWidget()) { | 
 |     return; | 
 |   } | 
 |   SetFWLCheckState(node_->GetCheckState()); | 
 |   GetNormalWidget()->Update(); | 
 | } | 
 |  | 
 | void CXFA_FFCheckButton::OnProcessMessage(CFWL_Message* pMessage) { | 
 |   old_delegate_->OnProcessMessage(pMessage); | 
 | } | 
 |  | 
 | void CXFA_FFCheckButton::OnProcessEvent(CFWL_Event* pEvent) { | 
 |   CXFA_FFField::OnProcessEvent(pEvent); | 
 |   switch (pEvent->GetType()) { | 
 |     case CFWL_Event::Type::CheckStateChanged: { | 
 |       CXFA_EventParam eParam(XFA_EVENT_Change); | 
 |       eParam.prev_text_ = node_->GetValue(XFA_ValuePicture::kRaw); | 
 |       CXFA_Node* exclNode = node_->GetExclGroupIfExists(); | 
 |       if (ProcessCommittedData()) { | 
 |         if (exclNode) { | 
 |           doc_view_->AddValidateNode(exclNode); | 
 |           doc_view_->AddCalculateNode(exclNode); | 
 |           exclNode->ProcessEvent(GetDocView(), XFA_AttributeValue::Change, | 
 |                                  &eParam); | 
 |         } | 
 |         node_->ProcessEvent(GetDocView(), XFA_AttributeValue::Change, &eParam); | 
 |       } else { | 
 |         SetFWLCheckState(node_->GetCheckState()); | 
 |       } | 
 |       if (exclNode) { | 
 |         exclNode->ProcessEvent(GetDocView(), XFA_AttributeValue::Click, | 
 |                                &eParam); | 
 |       } | 
 |       node_->ProcessEvent(GetDocView(), XFA_AttributeValue::Click, &eParam); | 
 |       break; | 
 |     } | 
 |     default: | 
 |       break; | 
 |   } | 
 |   old_delegate_->OnProcessEvent(pEvent); | 
 | } | 
 |  | 
 | void CXFA_FFCheckButton::OnDrawWidget(CFGAS_GEGraphics* pGraphics, | 
 |                                       const CFX_Matrix& matrix) { | 
 |   old_delegate_->OnDrawWidget(pGraphics, matrix); | 
 | } | 
 |  | 
 | FormFieldType CXFA_FFCheckButton::GetFormFieldType() { | 
 |   return FormFieldType::kXFA_CheckBox; | 
 | } |