| // Copyright 2014 PDFium Authors. All rights reserved. | 
 | // 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/fwl/cfwl_datetimepicker.h" | 
 |  | 
 | #include "xfa/fwl/cfwl_app.h" | 
 | #include "xfa/fwl/cfwl_event.h" | 
 | #include "xfa/fwl/cfwl_eventselectchanged.h" | 
 | #include "xfa/fwl/cfwl_messagemouse.h" | 
 | #include "xfa/fwl/cfwl_messagesetfocus.h" | 
 | #include "xfa/fwl/cfwl_notedriver.h" | 
 | #include "xfa/fwl/cfwl_themebackground.h" | 
 | #include "xfa/fwl/cfwl_widgetmgr.h" | 
 | #include "xfa/fwl/ifwl_themeprovider.h" | 
 |  | 
 | namespace { | 
 |  | 
 | const int kDateTimePickerHeight = 20; | 
 |  | 
 | }  // namespace | 
 | CFWL_DateTimePicker::CFWL_DateTimePicker(CFWL_App* app) | 
 |     : CFWL_Widget(app, | 
 |                   Properties{0, FWL_STYLEEXT_DTP_ShortDateFormat, 0}, | 
 |                   nullptr), | 
 |       m_pEdit(cppgc::MakeGarbageCollected<CFWL_DateTimeEdit>( | 
 |           app->GetHeap()->GetAllocationHandle(), | 
 |           app, | 
 |           Properties(), | 
 |           this)), | 
 |       m_pMonthCal(cppgc::MakeGarbageCollected<CFWL_MonthCalendar>( | 
 |           app->GetHeap()->GetAllocationHandle(), | 
 |           app, | 
 |           Properties{FWL_WGTSTYLE_Popup | FWL_WGTSTYLE_Border, 0, | 
 |                      FWL_WGTSTATE_Invisible}, | 
 |           this)) { | 
 |   m_pMonthCal->SetWidgetRect( | 
 |       CFX_RectF(0, 0, m_pMonthCal->GetAutosizedWidgetRect().Size())); | 
 |  | 
 |   RegisterEventTarget(m_pMonthCal); | 
 |   RegisterEventTarget(m_pEdit); | 
 | } | 
 |  | 
 | CFWL_DateTimePicker::~CFWL_DateTimePicker() = default; | 
 |  | 
 | void CFWL_DateTimePicker::PreFinalize() { | 
 |   UnregisterEventTarget(); | 
 |   CFWL_Widget::PreFinalize(); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::Trace(cppgc::Visitor* visitor) const { | 
 |   CFWL_Widget::Trace(visitor); | 
 |   visitor->Trace(m_pEdit); | 
 |   visitor->Trace(m_pMonthCal); | 
 | } | 
 |  | 
 | FWL_Type CFWL_DateTimePicker::GetClassID() const { | 
 |   return FWL_Type::DateTimePicker; | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::Update() { | 
 |   if (IsLocked()) | 
 |     return; | 
 |  | 
 |   m_ClientRect = GetClientRect(); | 
 |   m_pEdit->SetWidgetRect(m_ClientRect); | 
 |   ResetEditAlignment(); | 
 |   m_pEdit->Update(); | 
 |  | 
 |   m_fBtn = GetThemeProvider()->GetScrollBarWidth(); | 
 |   CFX_RectF rtMonthCal = m_pMonthCal->GetAutosizedWidgetRect(); | 
 |   CFX_RectF rtPopUp(rtMonthCal.left, rtMonthCal.top + kDateTimePickerHeight, | 
 |                     rtMonthCal.width, rtMonthCal.height); | 
 |   m_pMonthCal->SetWidgetRect(rtPopUp); | 
 |   m_pMonthCal->Update(); | 
 | } | 
 |  | 
 | FWL_WidgetHit CFWL_DateTimePicker::HitTest(const CFX_PointF& point) { | 
 |   CFX_RectF rect(0, 0, m_WidgetRect.width, m_WidgetRect.height); | 
 |   if (rect.Contains(point)) | 
 |     return FWL_WidgetHit::Edit; | 
 |   if (NeedsToShowButton()) | 
 |     rect.width += m_fBtn; | 
 |   if (rect.Contains(point)) | 
 |     return FWL_WidgetHit::Client; | 
 |   if (IsMonthCalendarVisible()) { | 
 |     if (m_pMonthCal->GetWidgetRect().Contains(point)) | 
 |       return FWL_WidgetHit::Client; | 
 |   } | 
 |   return FWL_WidgetHit::Unknown; | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::DrawWidget(CFGAS_GEGraphics* pGraphics, | 
 |                                      const CFX_Matrix& matrix) { | 
 |   if (!pGraphics) | 
 |     return; | 
 |  | 
 |   if (HasBorder()) | 
 |     DrawBorder(pGraphics, CFWL_Part::Border, matrix); | 
 |  | 
 |   if (!m_BtnRect.IsEmpty()) | 
 |     DrawDropDownButton(pGraphics, &matrix); | 
 |  | 
 |   if (m_pEdit) { | 
 |     CFX_RectF rtEdit = m_pEdit->GetWidgetRect(); | 
 |     CFX_Matrix mt(1, 0, 0, 1, rtEdit.left, rtEdit.top); | 
 |     mt.Concat(matrix); | 
 |     m_pEdit->DrawWidget(pGraphics, mt); | 
 |   } | 
 |   if (!IsMonthCalendarVisible()) | 
 |     return; | 
 |  | 
 |   CFX_RectF rtMonth = m_pMonthCal->GetWidgetRect(); | 
 |   CFX_Matrix mt(1, 0, 0, 1, rtMonth.left, rtMonth.top); | 
 |   mt.Concat(matrix); | 
 |   m_pMonthCal->DrawWidget(pGraphics, mt); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::GetCurSel(int32_t& iYear, | 
 |                                     int32_t& iMonth, | 
 |                                     int32_t& iDay) { | 
 |   iYear = m_iYear; | 
 |   iMonth = m_iMonth; | 
 |   iDay = m_iDay; | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::SetCurSel(int32_t iYear, | 
 |                                     int32_t iMonth, | 
 |                                     int32_t iDay) { | 
 |   if (iYear <= 0 || iYear >= 3000) | 
 |     return; | 
 |   if (iMonth <= 0 || iMonth >= 13) | 
 |     return; | 
 |   if (iDay <= 0 || iDay >= 32) | 
 |     return; | 
 |  | 
 |   m_iYear = iYear; | 
 |   m_iMonth = iMonth; | 
 |   m_iDay = iDay; | 
 |   m_pMonthCal->SetSelect(iYear, iMonth, iDay); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::SetEditText(const WideString& wsText) { | 
 |   if (!m_pEdit) | 
 |     return; | 
 |  | 
 |   m_pEdit->SetText(wsText); | 
 |   RepaintRect(m_ClientRect); | 
 |  | 
 |   CFWL_Event ev(CFWL_Event::Type::EditChanged); | 
 |   DispatchEvent(&ev); | 
 | } | 
 |  | 
 | WideString CFWL_DateTimePicker::GetEditText() const { | 
 |   return m_pEdit ? m_pEdit->GetText() : WideString(); | 
 | } | 
 |  | 
 | int32_t CFWL_DateTimePicker::GetEditTextLength() const { | 
 |   return m_pEdit ? m_pEdit->GetTextLength() : 0; | 
 | } | 
 |  | 
 | CFX_RectF CFWL_DateTimePicker::GetBBox() const { | 
 |   CFX_RectF rect = m_WidgetRect; | 
 |   if (NeedsToShowButton()) | 
 |     rect.width += m_fBtn; | 
 |   if (!IsMonthCalendarVisible()) | 
 |     return rect; | 
 |  | 
 |   CFX_RectF rtMonth = m_pMonthCal->GetWidgetRect(); | 
 |   rtMonth.Offset(m_WidgetRect.left, m_WidgetRect.top); | 
 |   rect.Union(rtMonth); | 
 |   return rect; | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::ModifyEditStylesEx(uint32_t dwStylesExAdded, | 
 |                                              uint32_t dwStylesExRemoved) { | 
 |   m_pEdit->ModifyStylesEx(dwStylesExAdded, dwStylesExRemoved); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::DrawDropDownButton(CFGAS_GEGraphics* pGraphics, | 
 |                                              const CFX_Matrix* pMatrix) { | 
 |   CFWL_ThemeBackground param; | 
 |   param.m_pWidget = this; | 
 |   param.m_iPart = CFWL_Part::DropDownButton; | 
 |   param.m_dwStates = m_iBtnState; | 
 |   param.m_pGraphics = pGraphics; | 
 |   param.m_PartRect = m_BtnRect; | 
 |   if (pMatrix) | 
 |     param.m_matrix.Concat(*pMatrix); | 
 |   GetThemeProvider()->DrawBackground(param); | 
 | } | 
 |  | 
 | WideString CFWL_DateTimePicker::FormatDateString(int32_t iYear, | 
 |                                                  int32_t iMonth, | 
 |                                                  int32_t iDay) { | 
 |   if (m_Properties.m_dwStyleExes & FWL_STYLEEXT_DTP_ShortDateFormat) | 
 |     return WideString::Format(L"%d-%d-%d", iYear, iMonth, iDay); | 
 |  | 
 |   return WideString::Format(L"%d Year %d Month %d Day", iYear, iMonth, iDay); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::ShowMonthCalendar(bool bActivate) { | 
 |   if (IsMonthCalendarVisible() == bActivate) | 
 |     return; | 
 |  | 
 |   if (bActivate) { | 
 |     CFX_RectF rtMonthCal = m_pMonthCal->GetAutosizedWidgetRect(); | 
 |     float fPopupMin = rtMonthCal.height; | 
 |     float fPopupMax = rtMonthCal.height; | 
 |     CFX_RectF rtAnchor = m_WidgetRect; | 
 |     rtAnchor.width = rtMonthCal.width; | 
 |     rtMonthCal.left = m_ClientRect.left; | 
 |     rtMonthCal.top = rtAnchor.Height(); | 
 |     GetPopupPos(fPopupMin, fPopupMax, rtAnchor, &rtMonthCal); | 
 |     m_pMonthCal->SetWidgetRect(rtMonthCal); | 
 |     if (m_iYear > 0 && m_iMonth > 0 && m_iDay > 0) | 
 |       m_pMonthCal->SetSelect(m_iYear, m_iMonth, m_iDay); | 
 |     m_pMonthCal->Update(); | 
 |   } | 
 |   if (bActivate) | 
 |     m_pMonthCal->RemoveStates(FWL_WGTSTATE_Invisible); | 
 |   else | 
 |     m_pMonthCal->SetStates(FWL_WGTSTATE_Invisible); | 
 |  | 
 |   if (bActivate) { | 
 |     CFWL_MessageSetFocus msg(m_pEdit, m_pMonthCal); | 
 |     m_pEdit->GetDelegate()->OnProcessMessage(&msg); | 
 |   } | 
 |  | 
 |   CFX_RectF rtInvalidate(0, 0, m_WidgetRect.width, m_WidgetRect.height); | 
 |   CFX_RectF rtCal = m_pMonthCal->GetWidgetRect(); | 
 |   rtInvalidate.Union(rtCal); | 
 |   rtInvalidate.Inflate(2, 2); | 
 |   RepaintRect(rtInvalidate); | 
 | } | 
 |  | 
 | bool CFWL_DateTimePicker::IsMonthCalendarVisible() const { | 
 |   return m_pMonthCal && m_pMonthCal->IsVisible(); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::ResetEditAlignment() { | 
 |   if (!m_pEdit) | 
 |     return; | 
 |  | 
 |   uint32_t dwAdd = 0; | 
 |   switch (m_Properties.m_dwStyleExes & FWL_STYLEEXT_DTP_EditHAlignMask) { | 
 |     case FWL_STYLEEXT_DTP_EditHCenter: { | 
 |       dwAdd |= FWL_STYLEEXT_EDT_HCenter; | 
 |       break; | 
 |     } | 
 |     case FWL_STYLEEXT_DTP_EditHFar: { | 
 |       dwAdd |= FWL_STYLEEXT_EDT_HFar; | 
 |       break; | 
 |     } | 
 |     default: { | 
 |       dwAdd |= FWL_STYLEEXT_EDT_HNear; | 
 |       break; | 
 |     } | 
 |   } | 
 |   switch (m_Properties.m_dwStyleExes & FWL_STYLEEXT_DTP_EditVAlignMask) { | 
 |     case FWL_STYLEEXT_DTP_EditVCenter: { | 
 |       dwAdd |= FWL_STYLEEXT_EDT_VCenter; | 
 |       break; | 
 |     } | 
 |     case FWL_STYLEEXT_DTP_EditVFar: { | 
 |       dwAdd |= FWL_STYLEEXT_EDT_VFar; | 
 |       break; | 
 |     } | 
 |     default: { | 
 |       dwAdd |= FWL_STYLEEXT_EDT_VNear; | 
 |       break; | 
 |     } | 
 |   } | 
 |   if (m_Properties.m_dwStyleExes & FWL_STYLEEXT_DTP_EditJustified) | 
 |     dwAdd |= FWL_STYLEEXT_EDT_Justified; | 
 |  | 
 |   m_pEdit->ModifyStylesEx(dwAdd, FWL_STYLEEXT_EDT_HAlignMask | | 
 |                                      FWL_STYLEEXT_EDT_HAlignModeMask | | 
 |                                      FWL_STYLEEXT_EDT_VAlignMask); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::ProcessSelChanged(int32_t iYear, | 
 |                                             int32_t iMonth, | 
 |                                             int32_t iDay) { | 
 |   m_iYear = iYear; | 
 |   m_iMonth = iMonth; | 
 |   m_iDay = iDay; | 
 |   m_pEdit->SetText(FormatDateString(m_iYear, m_iMonth, m_iDay)); | 
 |   m_pEdit->Update(); | 
 |   RepaintRect(m_ClientRect); | 
 |  | 
 |   CFWL_EventSelectChanged ev(this); | 
 |   ev.iYear = m_iYear; | 
 |   ev.iMonth = m_iMonth; | 
 |   ev.iDay = m_iDay; | 
 |   DispatchEvent(&ev); | 
 | } | 
 |  | 
 | bool CFWL_DateTimePicker::NeedsToShowButton() const { | 
 |   return m_Properties.m_dwStates & FWL_WGTSTATE_Focused || | 
 |          m_pMonthCal->GetStates() & FWL_WGTSTATE_Focused || | 
 |          m_pEdit->GetStates() & FWL_WGTSTATE_Focused; | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::OnProcessMessage(CFWL_Message* pMessage) { | 
 |   switch (pMessage->GetType()) { | 
 |     case CFWL_Message::Type::kSetFocus: | 
 |       OnFocusChanged(pMessage, true); | 
 |       break; | 
 |     case CFWL_Message::Type::kKillFocus: | 
 |       OnFocusChanged(pMessage, false); | 
 |       break; | 
 |     case CFWL_Message::Type::kMouse: { | 
 |       CFWL_MessageMouse* pMouse = static_cast<CFWL_MessageMouse*>(pMessage); | 
 |       switch (pMouse->m_dwCmd) { | 
 |         case FWL_MouseCommand::LeftButtonDown: | 
 |           OnLButtonDown(pMouse); | 
 |           break; | 
 |         case FWL_MouseCommand::LeftButtonUp: | 
 |           OnLButtonUp(pMouse); | 
 |           break; | 
 |         case FWL_MouseCommand::Move: | 
 |           OnMouseMove(pMouse); | 
 |           break; | 
 |         case FWL_MouseCommand::Leave: | 
 |           OnMouseLeave(pMouse); | 
 |           break; | 
 |         default: | 
 |           break; | 
 |       } | 
 |       break; | 
 |     } | 
 |     case CFWL_Message::Type::kKey: { | 
 |       if (m_pEdit->GetStates() & FWL_WGTSTATE_Focused) { | 
 |         m_pEdit->GetDelegate()->OnProcessMessage(pMessage); | 
 |         return; | 
 |       } | 
 |       break; | 
 |     } | 
 |     default: | 
 |       break; | 
 |   } | 
 |   // Dst target could be |this|, continue only if not destroyed by above. | 
 |   if (pMessage->GetDstTarget()) | 
 |     CFWL_Widget::OnProcessMessage(pMessage); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::OnDrawWidget(CFGAS_GEGraphics* pGraphics, | 
 |                                        const CFX_Matrix& matrix) { | 
 |   DrawWidget(pGraphics, matrix); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::OnFocusChanged(CFWL_Message* pMsg, bool bSet) { | 
 |   if (!pMsg) | 
 |     return; | 
 |  | 
 |   CFX_RectF rtInvalidate(m_BtnRect); | 
 |   if (bSet) { | 
 |     m_Properties.m_dwStates |= FWL_WGTSTATE_Focused; | 
 |     if (m_pEdit && !(m_pEdit->GetStylesEx() & FWL_STYLEEXT_EDT_ReadOnly)) { | 
 |       m_BtnRect = | 
 |           CFX_RectF(m_WidgetRect.width, 0, m_fBtn, m_WidgetRect.height - 1); | 
 |     } | 
 |     rtInvalidate = m_BtnRect; | 
 |     pMsg->SetDstTarget(m_pEdit); | 
 |     m_pEdit->GetDelegate()->OnProcessMessage(pMsg); | 
 |   } else { | 
 |     m_Properties.m_dwStates &= ~FWL_WGTSTATE_Focused; | 
 |     m_BtnRect = CFX_RectF(); | 
 |     if (IsMonthCalendarVisible()) | 
 |       ShowMonthCalendar(false); | 
 |     if (m_pEdit->GetStates() & FWL_WGTSTATE_Focused) { | 
 |       pMsg->SetSrcTarget(m_pEdit); | 
 |       m_pEdit->GetDelegate()->OnProcessMessage(pMsg); | 
 |     } | 
 |   } | 
 |   rtInvalidate.Inflate(2, 2); | 
 |   RepaintRect(rtInvalidate); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::OnLButtonDown(CFWL_MessageMouse* pMsg) { | 
 |   if (!pMsg) | 
 |     return; | 
 |   if (!m_BtnRect.Contains(pMsg->m_pos)) | 
 |     return; | 
 |  | 
 |   if (IsMonthCalendarVisible()) { | 
 |     ShowMonthCalendar(false); | 
 |     return; | 
 |   } | 
 |   ShowMonthCalendar(true); | 
 |  | 
 |   m_bLBtnDown = true; | 
 |   RepaintRect(m_ClientRect); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::OnLButtonUp(CFWL_MessageMouse* pMsg) { | 
 |   if (!pMsg) | 
 |     return; | 
 |  | 
 |   m_bLBtnDown = false; | 
 |   if (m_BtnRect.Contains(pMsg->m_pos)) | 
 |     m_iBtnState = CFWL_PartState_Hovered; | 
 |   else | 
 |     m_iBtnState = CFWL_PartState_Normal; | 
 |   RepaintRect(m_BtnRect); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::OnMouseMove(CFWL_MessageMouse* pMsg) { | 
 |   if (!m_BtnRect.Contains(pMsg->m_pos)) | 
 |     m_iBtnState = CFWL_PartState_Normal; | 
 |   RepaintRect(m_BtnRect); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::OnMouseLeave(CFWL_MessageMouse* pMsg) { | 
 |   if (!pMsg) | 
 |     return; | 
 |   m_iBtnState = CFWL_PartState_Normal; | 
 |   RepaintRect(m_BtnRect); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::GetPopupPos(float fMinHeight, | 
 |                                       float fMaxHeight, | 
 |                                       const CFX_RectF& rtAnchor, | 
 |                                       CFX_RectF* pPopupRect) { | 
 |   GetWidgetMgr()->GetAdapterPopupPos(this, fMinHeight, fMaxHeight, rtAnchor, | 
 |                                      pPopupRect); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::ClearText() { | 
 |   m_pEdit->ClearText(); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::SelectAll() { | 
 |   m_pEdit->SelectAll(); | 
 | } | 
 |  | 
 | void CFWL_DateTimePicker::ClearSelection() { | 
 |   m_pEdit->ClearSelection(); | 
 | } | 
 |  | 
 | Optional<WideString> CFWL_DateTimePicker::Copy() { | 
 |   return m_pEdit->Copy(); | 
 | } | 
 |  | 
 | Optional<WideString> CFWL_DateTimePicker::Cut() { | 
 |   return m_pEdit->Cut(); | 
 | } | 
 |  | 
 | bool CFWL_DateTimePicker::Paste(const WideString& wsPaste) { | 
 |   return m_pEdit->Paste(wsPaste); | 
 | } | 
 |  | 
 | bool CFWL_DateTimePicker::Undo() { | 
 |   return m_pEdit->Undo(); | 
 | } | 
 |  | 
 | bool CFWL_DateTimePicker::Redo() { | 
 |   return m_pEdit->Redo(); | 
 | } | 
 |  | 
 | bool CFWL_DateTimePicker::CanUndo() { | 
 |   return m_pEdit->CanUndo(); | 
 | } | 
 |  | 
 | bool CFWL_DateTimePicker::CanRedo() { | 
 |   return m_pEdit->CanRedo(); | 
 | } |