From 134ac9105586407eb3b1e06001101ff893dd4a31 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Fri, 15 Sep 2017 15:32:01 -0700 Subject: Introduce CPWL_Wnd::PrivateData class. First step in passing ownership of this memory to the CPWL_Wnd. In turn, nest two other classes that also require PrivateData to satisfy nesting rules. Move one stray #define to the appropriate file while at it. Change-Id: I565934565421f5843a3b792b3bdc21b5e8839eb8 Reviewed-on: https://pdfium-review.googlesource.com/14170 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- fpdfsdk/formfiller/cffl_combobox.h | 5 +- fpdfsdk/formfiller/cffl_formfiller.cpp | 14 ++-- fpdfsdk/formfiller/cffl_formfiller.h | 7 +- fpdfsdk/formfiller/cffl_interactiveformfiller.cpp | 36 +++++----- fpdfsdk/formfiller/cffl_interactiveformfiller.h | 12 ++-- fpdfsdk/formfiller/cffl_textfield.h | 5 +- fpdfsdk/pwl/cpwl_edit.cpp | 2 +- fpdfsdk/pwl/cpwl_edit.h | 12 ++-- fpdfsdk/pwl/cpwl_wnd.cpp | 18 ++--- fpdfsdk/pwl/cpwl_wnd.h | 88 ++++++++++++----------- 10 files changed, 106 insertions(+), 93 deletions(-) diff --git a/fpdfsdk/formfiller/cffl_combobox.h b/fpdfsdk/formfiller/cffl_combobox.h index 7d5a38ef56..dc8441d1cd 100644 --- a/fpdfsdk/formfiller/cffl_combobox.h +++ b/fpdfsdk/formfiller/cffl_combobox.h @@ -19,7 +19,8 @@ struct FFL_ComboBoxState { CFX_WideString sValue; }; -class CFFL_ComboBox : public CFFL_TextObject, public IPWL_FocusHandler { +class CFFL_ComboBox : public CFFL_TextObject, + public CPWL_Wnd::FocusHandlerIface { public: CFFL_ComboBox(CPDFSDK_FormFillEnvironment* pApp, CPDFSDK_Widget* pWidget); ~CFFL_ComboBox() override; @@ -45,7 +46,7 @@ class CFFL_ComboBox : public CFFL_TextObject, public IPWL_FocusHandler { bool IsFieldFull(CPDFSDK_PageView* pPageView) override; #endif - // IPWL_FocusHandler: + // CPWL_Wnd::FocusHandlerIface: void OnSetFocus(CPWL_Edit* pEdit) override; private: diff --git a/fpdfsdk/formfiller/cffl_formfiller.cpp b/fpdfsdk/formfiller/cffl_formfiller.cpp index f405e3578d..e59cd0520f 100644 --- a/fpdfsdk/formfiller/cffl_formfiller.cpp +++ b/fpdfsdk/formfiller/cffl_formfiller.cpp @@ -37,8 +37,7 @@ CFFL_FormFiller::~CFFL_FormFiller() { void CFFL_FormFiller::DestroyWindows() { for (const auto& it : m_Maps) { CPWL_Wnd* pWnd = it.second; - CFFL_PrivateData* pData = - static_cast(pWnd->GetAttachedData()); + auto* pData = static_cast(pWnd->GetAttachedData()); pWnd->InvalidateProvider(this); pWnd->Destroy(); delete pWnd; @@ -359,7 +358,7 @@ CPWL_Wnd* CFFL_FormFiller::GetPDFWindow(CPDFSDK_PageView* pPageView, CPWL_Wnd::CreateParams cp = GetCreateParam(); cp.pAttachedWidget.Reset(m_pWidget.Get()); - CFFL_PrivateData* pPrivateData = new CFFL_PrivateData; + auto* pPrivateData = new CFFL_PrivateData; pPrivateData->pWidget = m_pWidget.Get(); pPrivateData->pPageView = pPageView; pPrivateData->nWidgetAge = m_pWidget->GetAppearanceAge(); @@ -384,18 +383,17 @@ void CFFL_FormFiller::DestroyPDFWindow(CPDFSDK_PageView* pPageView) { return; CPWL_Wnd* pWnd = it->second; - CFFL_PrivateData* pData = (CFFL_PrivateData*)pWnd->GetAttachedData(); + auto* pData = static_cast(pWnd->GetAttachedData()); pWnd->Destroy(); delete pWnd; delete pData; - m_Maps.erase(it); } -CFX_Matrix CFFL_FormFiller::GetWindowMatrix(void* pAttachedData) { +CFX_Matrix CFFL_FormFiller::GetWindowMatrix(CPWL_Wnd::PrivateData* pAttached) { CFX_Matrix mt; - auto* pPrivateData = reinterpret_cast(pAttachedData); - if (!pAttachedData || !pPrivateData->pPageView) + auto* pPrivateData = static_cast(pAttached); + if (!pPrivateData || !pPrivateData->pPageView) return mt; CFX_Matrix mtPageView; diff --git a/fpdfsdk/formfiller/cffl_formfiller.h b/fpdfsdk/formfiller/cffl_formfiller.h index b9af3897e4..e9c2c551d2 100644 --- a/fpdfsdk/formfiller/cffl_formfiller.h +++ b/fpdfsdk/formfiller/cffl_formfiller.h @@ -19,7 +19,8 @@ class CPDFSDK_FormFillEnvironment; class CPDFSDK_PageView; class CPDFSDK_Widget; -class CFFL_FormFiller : public IPWL_Provider, public CPWL_TimerHandler { +class CFFL_FormFiller : public CPWL_Wnd::ProviderIface, + public CPWL_TimerHandler { public: CFFL_FormFiller(CPDFSDK_FormFillEnvironment* pFormFillEnv, CPDFSDK_Widget* pWidget); @@ -84,8 +85,8 @@ class CFFL_FormFiller : public IPWL_Provider, public CPWL_TimerHandler { void TimerProc() override; CFX_SystemHandler* GetSystemHandler() const override; - // IPWL_Provider - CFX_Matrix GetWindowMatrix(void* pAttachedData) override; + // CPWL_Wnd::ProviderIface: + CFX_Matrix GetWindowMatrix(CPWL_Wnd::PrivateData* pAttached) override; virtual void GetActionData(CPDFSDK_PageView* pPageView, CPDF_AAction::AActionType type, diff --git a/fpdfsdk/formfiller/cffl_interactiveformfiller.cpp b/fpdfsdk/formfiller/cffl_interactiveformfiller.cpp index 10cefd204b..425c2add35 100644 --- a/fpdfsdk/formfiller/cffl_interactiveformfiller.cpp +++ b/fpdfsdk/formfiller/cffl_interactiveformfiller.cpp @@ -544,14 +544,15 @@ void CFFL_InteractiveFormFiller::UnRegisterFormFiller(CPDFSDK_Annot* pAnnot) { m_Maps.erase(it); } -void CFFL_InteractiveFormFiller::QueryWherePopup(void* pPrivateData, - float fPopupMin, - float fPopupMax, - bool* bBottom, - float* fPopupRet) { - CFFL_PrivateData* pData = reinterpret_cast(pPrivateData); +void CFFL_InteractiveFormFiller::QueryWherePopup( + CPWL_Wnd::PrivateData* pAttached, + float fPopupMin, + float fPopupMax, + bool* bBottom, + float* fPopupRet) { + auto* pData = static_cast(pAttached); CPDFSDK_Widget* pWidget = pData->pWidget; - auto* pPage = pWidget->GetPDFPage(); + CPDF_Page* pPage = pWidget->GetPDFPage(); CFX_FloatRect rcPageView(0, pPage->GetPageHeight(), pPage->GetPageWidth(), 0); rcPageView.Normalize(); @@ -767,9 +768,10 @@ bool CFFL_InteractiveFormFiller::OnFull(CPDFSDK_Annot::ObservedPtr* pAnnot, return true; } -bool CFFL_InteractiveFormFiller::OnPopupPreOpen(void* pPrivateData, - uint32_t nFlag) { - CFFL_PrivateData* pData = reinterpret_cast(pPrivateData); +bool CFFL_InteractiveFormFiller::OnPopupPreOpen( + CPWL_Wnd::PrivateData* pAttached, + uint32_t nFlag) { + auto* pData = static_cast(pAttached); ASSERT(pData); ASSERT(pData->pWidget); @@ -777,9 +779,10 @@ bool CFFL_InteractiveFormFiller::OnPopupPreOpen(void* pPrivateData, return OnPreOpen(&pObserved, pData->pPageView, nFlag) || !pObserved; } -bool CFFL_InteractiveFormFiller::OnPopupPostOpen(void* pPrivateData, - uint32_t nFlag) { - CFFL_PrivateData* pData = reinterpret_cast(pPrivateData); +bool CFFL_InteractiveFormFiller::OnPopupPostOpen( + CPWL_Wnd::PrivateData* pAttached, + uint32_t nFlag) { + auto* pData = static_cast(pAttached); ASSERT(pData); ASSERT(pData->pWidget); @@ -856,7 +859,7 @@ bool CFFL_InteractiveFormFiller::IsValidAnnot(CPDFSDK_PageView* pPageView, } std::pair CFFL_InteractiveFormFiller::OnBeforeKeyStroke( - void* pPrivateData, + CPWL_Wnd::PrivateData* pAttached, CFX_WideString& strChange, const CFX_WideString& strChangeEx, int nSelStart, @@ -864,8 +867,7 @@ std::pair CFFL_InteractiveFormFiller::OnBeforeKeyStroke( bool bKeyDown, uint32_t nFlag) { // Copy the private data since the window owning it may not survive. - CFFL_PrivateData privateData = - *reinterpret_cast(pPrivateData); + CFFL_PrivateData privateData = *static_cast(pAttached); ASSERT(privateData.pWidget); CFFL_FormFiller* pFormFiller = GetFormFiller(privateData.pWidget, false); @@ -921,7 +923,7 @@ std::pair CFFL_InteractiveFormFiller::OnBeforeKeyStroke( privateData.pPageView, nValueAge == privateData.pWidget->GetValueAge()); if (!pWnd) return {true, true}; - privateData = *reinterpret_cast(pWnd->GetAttachedData()); + privateData = *static_cast(pWnd->GetAttachedData()); bExit = true; } if (fa.bRC) { diff --git a/fpdfsdk/formfiller/cffl_interactiveformfiller.h b/fpdfsdk/formfiller/cffl_interactiveformfiller.h index b71f3a6ef7..ddab2adcda 100644 --- a/fpdfsdk/formfiller/cffl_interactiveformfiller.h +++ b/fpdfsdk/formfiller/cffl_interactiveformfiller.h @@ -126,13 +126,13 @@ class CFFL_InteractiveFormFiller : public IPWL_Filler_Notify { std::map>; // IPWL_Filler_Notify: - void QueryWherePopup(void* pPrivateData, + void QueryWherePopup(CPWL_Wnd::PrivateData* pAttached, float fPopupMin, float fPopupMax, bool* bBottom, float* fPopupRet) override; // Returns {bRC, bExit}. - std::pair OnBeforeKeyStroke(void* pPrivateData, + std::pair OnBeforeKeyStroke(CPWL_Wnd::PrivateData* pAttached, CFX_WideString& strChange, const CFX_WideString& strChangeEx, int nSelStart, @@ -140,8 +140,10 @@ class CFFL_InteractiveFormFiller : public IPWL_Filler_Notify { bool bKeyDown, uint32_t nFlag) override; #ifdef PDF_ENABLE_XFA - bool OnPopupPreOpen(void* pPrivateData, uint32_t nFlag) override; - bool OnPopupPostOpen(void* pPrivateData, uint32_t nFlag) override; + bool OnPopupPreOpen(CPWL_Wnd::PrivateData* pAttached, + uint32_t nFlag) override; + bool OnPopupPostOpen(CPWL_Wnd::PrivateData* pAttached, + uint32_t nFlag) override; void SetFocusAnnotTab(CPDFSDK_Annot* pWidget, bool bSameField, bool bNext); #endif // PDF_ENABLE_XFA void UnRegisterFormFiller(CPDFSDK_Annot* pAnnot); @@ -151,7 +153,7 @@ class CFFL_InteractiveFormFiller : public IPWL_Filler_Notify { bool m_bNotifying; }; -class CFFL_PrivateData { +class CFFL_PrivateData : public CPWL_Wnd::PrivateData { public: CPDFSDK_Widget* pWidget; CPDFSDK_PageView* pPageView; diff --git a/fpdfsdk/formfiller/cffl_textfield.h b/fpdfsdk/formfiller/cffl_textfield.h index 6893516142..2a99413398 100644 --- a/fpdfsdk/formfiller/cffl_textfield.h +++ b/fpdfsdk/formfiller/cffl_textfield.h @@ -24,7 +24,8 @@ struct FFL_TextFieldState { CFX_WideString sValue; }; -class CFFL_TextField : public CFFL_TextObject, public IPWL_FocusHandler { +class CFFL_TextField : public CFFL_TextObject, + public CPWL_Wnd::FocusHandlerIface { public: CFFL_TextField(CPDFSDK_FormFillEnvironment* pApp, CPDFSDK_Widget* pWidget); ~CFFL_TextField() override; @@ -50,7 +51,7 @@ class CFFL_TextField : public CFFL_TextObject, public IPWL_FocusHandler { bool IsFieldFull(CPDFSDK_PageView* pPageView) override; #endif - // IPWL_FocusHandler: + // CPWL_Wnd::FocusHandlerIface: void OnSetFocus(CPWL_Edit* pEdit) override; private: diff --git a/fpdfsdk/pwl/cpwl_edit.cpp b/fpdfsdk/pwl/cpwl_edit.cpp index 4558e432ac..8fbafd0ca0 100644 --- a/fpdfsdk/pwl/cpwl_edit.cpp +++ b/fpdfsdk/pwl/cpwl_edit.cpp @@ -333,7 +333,7 @@ bool CPWL_Edit::OnRButtonUp(const CFX_PointF& point, uint32_t nFlag) { void CPWL_Edit::OnSetFocus() { SetEditCaret(true); if (!IsReadOnly()) { - if (IPWL_FocusHandler* pFocusHandler = GetFocusHandler()) + if (CPWL_Wnd::FocusHandlerIface* pFocusHandler = GetFocusHandler()) pFocusHandler->OnSetFocus(this); } m_bFocus = true; diff --git a/fpdfsdk/pwl/cpwl_edit.h b/fpdfsdk/pwl/cpwl_edit.h index ef12e9884a..3e8de04b47 100644 --- a/fpdfsdk/pwl/cpwl_edit.h +++ b/fpdfsdk/pwl/cpwl_edit.h @@ -13,18 +13,20 @@ #include "core/fxcrt/cfx_unowned_ptr.h" #include "fpdfsdk/pwl/cpwl_edit_ctrl.h" +#define PWL_CLASSNAME_EDIT "CPWL_Edit" + class IPWL_Filler_Notify { public: virtual ~IPWL_Filler_Notify() {} // Must write to |bBottom| and |fPopupRet|. - virtual void QueryWherePopup(void* pPrivateData, + virtual void QueryWherePopup(CPWL_Wnd::PrivateData* pAttached, float fPopupMin, float fPopupMax, bool* bBottom, float* fPopupRet) = 0; virtual std::pair OnBeforeKeyStroke( - void* pPrivateData, + CPWL_Wnd::PrivateData* pAttached, CFX_WideString& strChange, const CFX_WideString& strChangeEx, int nSelStart, @@ -32,8 +34,10 @@ class IPWL_Filler_Notify { bool bKeyDown, uint32_t nFlag) = 0; #ifdef PDF_ENABLE_XFA - virtual bool OnPopupPreOpen(void* pPrivateData, uint32_t nFlag) = 0; - virtual bool OnPopupPostOpen(void* pPrivateData, uint32_t nFlag) = 0; + virtual bool OnPopupPreOpen(CPWL_Wnd::PrivateData* pAttached, + uint32_t nFlag) = 0; + virtual bool OnPopupPostOpen(CPWL_Wnd::PrivateData* pAttached, + uint32_t nFlag) = 0; #endif // PDF_ENABLE_XFA }; diff --git a/fpdfsdk/pwl/cpwl_wnd.cpp b/fpdfsdk/pwl/cpwl_wnd.cpp index e259d1f9ef..401fed3f9c 100644 --- a/fpdfsdk/pwl/cpwl_wnd.cpp +++ b/fpdfsdk/pwl/cpwl_wnd.cpp @@ -44,6 +44,8 @@ CPWL_Wnd::CreateParams::CreateParams() CPWL_Wnd::CreateParams::CreateParams(const CreateParams& other) = default; +CPWL_Wnd::CreateParams::~CreateParams() = default; + class CPWL_MsgControl : public CFX_Observable { friend class CPWL_Wnd; @@ -181,12 +183,12 @@ void CPWL_Wnd::OnCreated() {} void CPWL_Wnd::OnDestroy() {} -void CPWL_Wnd::InvalidateFocusHandler(IPWL_FocusHandler* handler) { +void CPWL_Wnd::InvalidateFocusHandler(FocusHandlerIface* handler) { if (m_CreationParams.pFocusHandler == handler) m_CreationParams.pFocusHandler = nullptr; } -void CPWL_Wnd::InvalidateProvider(IPWL_Provider* provider) { +void CPWL_Wnd::InvalidateProvider(ProviderIface* provider) { if (m_CreationParams.pProvider.Get() == provider) m_CreationParams.pProvider.Reset(); } @@ -482,8 +484,8 @@ const CPWL_Dash& CPWL_Wnd::GetBorderDash() const { return m_CreationParams.sDash; } -void* CPWL_Wnd::GetAttachedData() const { - return m_CreationParams.pAttachedData; +CPWL_Wnd::PrivateData* CPWL_Wnd::GetAttachedData() const { + return m_CreationParams.pAttachedData.Get(); } CPWL_ScrollBar* CPWL_Wnd::GetVScrollBar() const { @@ -670,11 +672,11 @@ CFX_SystemHandler* CPWL_Wnd::GetSystemHandler() const { return m_CreationParams.pSystemHandler; } -IPWL_FocusHandler* CPWL_Wnd::GetFocusHandler() const { - return m_CreationParams.pFocusHandler; +CPWL_Wnd::FocusHandlerIface* CPWL_Wnd::GetFocusHandler() const { + return m_CreationParams.pFocusHandler.Get(); } -IPWL_Provider* CPWL_Wnd::GetProvider() const { +CPWL_Wnd::ProviderIface* CPWL_Wnd::GetProvider() const { return m_CreationParams.pProvider.Get(); } @@ -718,7 +720,7 @@ void CPWL_Wnd::SetTransparency(int32_t nTransparency) { CFX_Matrix CPWL_Wnd::GetWindowMatrix() const { CFX_Matrix mt = GetChildToRoot(); - if (IPWL_Provider* pProvider = GetProvider()) + if (ProviderIface* pProvider = GetProvider()) mt.Concat(pProvider->GetWindowMatrix(GetAttachedData())); return mt; } diff --git a/fpdfsdk/pwl/cpwl_wnd.h b/fpdfsdk/pwl/cpwl_wnd.h index 4074e9369f..2fd5077a86 100644 --- a/fpdfsdk/pwl/cpwl_wnd.h +++ b/fpdfsdk/pwl/cpwl_wnd.h @@ -22,10 +22,8 @@ class CPWL_Edit; class CPWL_MsgControl; class CPWL_ScrollBar; -class CPWL_Wnd; class CFX_SystemHandler; class IPVT_FontMap; -class IPWL_Provider; struct PWL_SCROLL_INFO; // window styles @@ -65,8 +63,6 @@ struct PWL_SCROLL_INFO; // combobox styles #define PCBS_ALLOWCUSTOMTEXT 0x0001L -#define PWL_CLASSNAME_EDIT "CPWL_Edit" - struct CPWL_Dash { CPWL_Dash() : nDash(0), nGap(0), nPhase(0) {} CPWL_Dash(int32_t dash, int32_t gap, int32_t phase) @@ -104,47 +100,53 @@ inline bool operator!=(const CFX_Color& c1, const CFX_Color& c2) { #define PWL_DEFAULT_BLACKCOLOR CFX_Color(COLORTYPE_GRAY, 0) #define PWL_DEFAULT_WHITECOLOR CFX_Color(COLORTYPE_GRAY, 1) -class IPWL_Provider : public CFX_Observable { +class CPWL_Wnd : public CPWL_TimerHandler, public CFX_Observable { public: - virtual ~IPWL_Provider() {} + class PrivateData { + protected: + ~PrivateData() {} + }; - // get a matrix which map user space to CWnd client space - virtual CFX_Matrix GetWindowMatrix(void* pAttachedData) = 0; -}; + class ProviderIface : public CFX_Observable { + public: + virtual ~ProviderIface() {} -class IPWL_FocusHandler { - public: - virtual ~IPWL_FocusHandler() {} - virtual void OnSetFocus(CPWL_Edit* pEdit) = 0; -}; + // get a matrix which map user space to CWnd client space + virtual CFX_Matrix GetWindowMatrix(PrivateData* pAttached) = 0; + }; -class CPWL_Wnd : public CPWL_TimerHandler, public CFX_Observable { - public: - struct CreateParams { + class FocusHandlerIface { + public: + virtual ~FocusHandlerIface() {} + virtual void OnSetFocus(CPWL_Edit* pEdit) = 0; + }; + + class CreateParams { public: CreateParams(); CreateParams(const CreateParams& other); - - CFX_FloatRect rcRectWnd; // required - CFX_SystemHandler* pSystemHandler; // required - IPVT_FontMap* pFontMap; // required - IPWL_Provider::ObservedPtr pProvider; // required - IPWL_FocusHandler* pFocusHandler; // optional - uint32_t dwFlags; // optional - CFX_Color sBackgroundColor; // optional - CPDFSDK_Widget::ObservedPtr pAttachedWidget; // required - BorderStyle nBorderStyle; // optional - int32_t dwBorderWidth; // optional - CFX_Color sBorderColor; // optional - CFX_Color sTextColor; // optional - int32_t nTransparency; // optional - float fFontSize; // optional - CPWL_Dash sDash; // optional - void* pAttachedData; // optional - CPWL_Wnd* pParentWnd; // ignore - CPWL_MsgControl* pMsgControl; // ignore - int32_t eCursorType; // ignore - CFX_Matrix mtChild; // ignore + ~CreateParams(); + + CFX_FloatRect rcRectWnd; // required + CFX_SystemHandler* pSystemHandler; // required + IPVT_FontMap* pFontMap; // required + ProviderIface::ObservedPtr pProvider; // required + CFX_UnownedPtr pFocusHandler; // optional + uint32_t dwFlags; // optional + CFX_Color sBackgroundColor; // optional + CPDFSDK_Widget::ObservedPtr pAttachedWidget; // required + BorderStyle nBorderStyle; // optional + int32_t dwBorderWidth; // optional + CFX_Color sBorderColor; // optional + CFX_Color sTextColor; // optional + int32_t nTransparency; // optional + float fFontSize; // optional + CPWL_Dash sDash; // optional + CFX_UnownedPtr pAttachedData; // optional + CPWL_Wnd* pParentWnd; // ignore + CPWL_MsgControl* pMsgControl; // ignore + int32_t eCursorType; // ignore + CFX_Matrix mtChild; // ignore }; CPWL_Wnd(); @@ -182,8 +184,8 @@ class CPWL_Wnd : public CPWL_TimerHandler, public CFX_Observable { virtual CFX_FloatRect GetFocusRect() const; virtual CFX_FloatRect GetClientRect() const; - void InvalidateFocusHandler(IPWL_FocusHandler* handler); - void InvalidateProvider(IPWL_Provider* provider); + void InvalidateFocusHandler(FocusHandlerIface* handler); + void InvalidateProvider(ProviderIface* provider); void Create(const CreateParams& cp); void Destroy(); void Move(const CFX_FloatRect& rcNew, bool bReset, bool bRefresh); @@ -220,7 +222,7 @@ class CPWL_Wnd : public CPWL_TimerHandler, public CFX_Observable { const CFX_FloatRect& GetClipRect() const; CPWL_Wnd* GetParentWindow() const; - void* GetAttachedData() const; + PrivateData* GetAttachedData() const; bool WndHitTest(const CFX_PointF& point) const; bool ClientHitTest(const CFX_PointF& point) const; @@ -234,8 +236,8 @@ class CPWL_Wnd : public CPWL_TimerHandler, public CFX_Observable { CPWL_ScrollBar* GetVScrollBar() const; IPVT_FontMap* GetFontMap() const; - IPWL_Provider* GetProvider() const; - IPWL_FocusHandler* GetFocusHandler() const; + ProviderIface* GetProvider() const; + FocusHandlerIface* GetFocusHandler() const; int32_t GetTransparency(); void SetTransparency(int32_t nTransparency); -- cgit v1.2.3