// 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 "fpdfsdk/include/pdfwindow/PWL_Note.h"

#include "core/include/fxge/fx_ge.h"
#include "fpdfsdk/include/pdfwindow/PWL_Button.h"
#include "fpdfsdk/include/pdfwindow/PWL_Caret.h"
#include "fpdfsdk/include/pdfwindow/PWL_Edit.h"
#include "fpdfsdk/include/pdfwindow/PWL_EditCtrl.h"
#include "fpdfsdk/include/pdfwindow/PWL_Label.h"
#include "fpdfsdk/include/pdfwindow/PWL_ListCtrl.h"
#include "fpdfsdk/include/pdfwindow/PWL_ScrollBar.h"
#include "fpdfsdk/include/pdfwindow/PWL_Utils.h"
#include "fpdfsdk/include/pdfwindow/PWL_Wnd.h"

#define POPUP_ITEM_HEAD_BOTTOM 3.0f
#define POPUP_ITEM_BOTTOMWIDTH 1.0f
#define POPUP_ITEM_SIDEMARGIN 3.0f
#define POPUP_ITEM_SPACE 4.0f
#define POPUP_ITEM_TEXT_INDENT 2.0f
#define POPUP_ITEM_BORDERCOLOR \
  CPWL_Color(COLORTYPE_RGB, 80 / 255.0f, 80 / 255.0f, 80 / 255.0f)

#define IsFloatZero(f) ((f) < 0.0001 && (f) > -0.0001)
#define IsFloatBigger(fa, fb) ((fa) > (fb) && !IsFloatZero((fa) - (fb)))
#define IsFloatSmaller(fa, fb) ((fa) < (fb) && !IsFloatZero((fa) - (fb)))
#define IsFloatEqual(fa, fb) IsFloatZero((fa) - (fb))

CPWL_Note_Options::CPWL_Note_Options() : m_pText(NULL) {}

CPWL_Note_Options::~CPWL_Note_Options() {}

void CPWL_Note_Options::SetTextColor(const CPWL_Color& color) {
  CPWL_Wnd::SetTextColor(color);

  if (m_pText)
    m_pText->SetTextColor(color);
}

void CPWL_Note_Options::RePosChildWnd() {
  if (IsValid()) {
    CFX_FloatRect rcClient = GetClientRect();

    if (rcClient.Width() > 15.0f) {
      rcClient.right -= 15.0f;
      m_pText->Move(rcClient, TRUE, FALSE);
      m_pText->SetVisible(TRUE);
    } else {
      m_pText->Move(CFX_FloatRect(0, 0, 0, 0), TRUE, FALSE);
      m_pText->SetVisible(FALSE);
    }
  }
}

void CPWL_Note_Options::CreateChildWnd(const PWL_CREATEPARAM& cp) {
  m_pText = new CPWL_Label;
  PWL_CREATEPARAM tcp = cp;
  tcp.pParentWnd = this;
  tcp.dwFlags = PWS_CHILD | PWS_VISIBLE;
  m_pText->Create(tcp);
}

void CPWL_Note_Options::SetText(const CFX_WideString& sText) {
  m_pText->SetText(sText.c_str());
}

void CPWL_Note_Options::DrawThisAppearance(CFX_RenderDevice* pDevice,
                                           CFX_Matrix* pUser2Device) {
  CPWL_Wnd::DrawThisAppearance(pDevice, pUser2Device);

  CFX_FloatRect rcClient = GetClientRect();
  rcClient.left = rcClient.right - 15.0f;

  CFX_FloatPoint ptCenter =
      CFX_FloatPoint((rcClient.left + rcClient.right) * 0.5f,
                     (rcClient.top + rcClient.bottom) * 0.5f);

  CFX_FloatPoint pt1(ptCenter.x - 2.0f, ptCenter.y + 2.0f * 0.5f);
  CFX_FloatPoint pt2(ptCenter.x + 2.0f, ptCenter.y + 2.0f * 0.5f);
  CFX_FloatPoint pt3(ptCenter.x, ptCenter.y - 3.0f * 0.5f);

  CFX_PathData path;

  path.SetPointCount(4);
  path.SetPoint(0, pt1.x, pt1.y, FXPT_MOVETO);
  path.SetPoint(1, pt2.x, pt2.y, FXPT_LINETO);
  path.SetPoint(2, pt3.x, pt3.y, FXPT_LINETO);
  path.SetPoint(3, pt1.x, pt1.y, FXPT_LINETO);

  pDevice->DrawPath(
      &path, pUser2Device, NULL,
      CPWL_Utils::PWLColorToFXColor(GetTextColor(), GetTransparency()), 0,
      FXFILL_ALTERNATE);
}

CFX_FloatRect CPWL_Note_Options::GetContentRect() const {
  CFX_FloatRect rcText = m_pText->GetContentRect();
  rcText.right += 15.0f;
  return rcText;
}

CPWL_Note_Edit::CPWL_Note_Edit()
    : m_bEnableNotify(TRUE),
      m_fOldItemHeight(0.0f),
      m_bSizeChanged(FALSE),
      m_fOldMin(0.0f),
      m_fOldMax(0.0f) {}

CPWL_Note_Edit::~CPWL_Note_Edit() {}

void CPWL_Note_Edit::RePosChildWnd() {
  m_bEnableNotify = FALSE;
  CPWL_Edit::RePosChildWnd();
  m_bEnableNotify = TRUE;

  m_fOldItemHeight = GetContentRect().Height();
}

void CPWL_Note_Edit::SetText(const FX_WCHAR* csText) {
  m_bEnableNotify = FALSE;
  CPWL_Edit::SetText(csText);
  m_bEnableNotify = TRUE;
  m_fOldItemHeight = GetContentRect().Height();
}

void CPWL_Note_Edit::OnSetFocus() {
  m_bEnableNotify = FALSE;
  CPWL_Edit::OnSetFocus();
  m_bEnableNotify = TRUE;

  EnableSpellCheck(TRUE);
}

void CPWL_Note_Edit::OnKillFocus() {
  EnableSpellCheck(FALSE);

  if (CPWL_Wnd* pParent = GetParentWindow()) {
    if (CPWL_Wnd* pGrand = pParent->GetParentWindow()) {
      ASSERT(pGrand->GetClassName() == "CPWL_NoteItem");

      CPWL_NoteItem* pNoteItem = (CPWL_NoteItem*)pGrand;

      pNoteItem->OnContentsValidate();
    }
  }

  CPWL_Edit::OnKillFocus();
}

void CPWL_Note_Edit::OnNotify(CPWL_Wnd* pWnd,
                              FX_DWORD msg,
                              intptr_t wParam,
                              intptr_t lParam) {
  if (m_bEnableNotify) {
    if (wParam == SBT_VSCROLL) {
      switch (msg) {
        case PNM_SETSCROLLINFO:
          if (PWL_SCROLL_INFO* pInfo = (PWL_SCROLL_INFO*)lParam) {
            if (!IsFloatEqual(pInfo->fContentMax, m_fOldMax) ||
                !IsFloatEqual(pInfo->fContentMin, m_fOldMin)) {
              m_bSizeChanged = TRUE;
              if (CPWL_Wnd* pParent = GetParentWindow()) {
                pParent->OnNotify(this, PNM_NOTEEDITCHANGED, 0, 0);
              }

              m_fOldMax = pInfo->fContentMax;
              m_fOldMin = pInfo->fContentMin;
              return;
            }
          }
      }
    }
  }

  CPWL_Edit::OnNotify(pWnd, msg, wParam, lParam);

  if (m_bEnableNotify) {
    switch (msg) {
      case PNM_SETCARETINFO:
        if (PWL_CARET_INFO* pInfo = (PWL_CARET_INFO*)wParam) {
          PWL_CARET_INFO newInfo = *pInfo;
          newInfo.bVisible = TRUE;
          newInfo.ptHead = ChildToParent(pInfo->ptHead);
          newInfo.ptFoot = ChildToParent(pInfo->ptFoot);

          if (CPWL_Wnd* pParent = GetParentWindow()) {
            pParent->OnNotify(this, PNM_SETCARETINFO, (intptr_t)&newInfo, 0);
          }
        }
        break;
    }
  }
}

FX_FLOAT CPWL_Note_Edit::GetItemHeight(FX_FLOAT fLimitWidth) {
  if (fLimitWidth > 0) {
    if (!m_bSizeChanged)
      return m_fOldItemHeight;

    m_bSizeChanged = FALSE;

    EnableNotify(FALSE);
    EnableRefresh(FALSE);
    m_pEdit->EnableNotify(FALSE);

    Move(CFX_FloatRect(0, 0, fLimitWidth, 0), TRUE, FALSE);
    FX_FLOAT fRet = GetContentRect().Height();

    m_pEdit->EnableNotify(TRUE);
    EnableNotify(TRUE);
    EnableRefresh(TRUE);

    return fRet;
  }

  return 0;
}

FX_FLOAT CPWL_Note_Edit::GetItemLeftMargin() {
  return POPUP_ITEM_TEXT_INDENT;
}

FX_FLOAT CPWL_Note_Edit::GetItemRightMargin() {
  return POPUP_ITEM_TEXT_INDENT;
}

CPWL_Note_LBBox::CPWL_Note_LBBox() {}

CPWL_Note_LBBox::~CPWL_Note_LBBox() {}

void CPWL_Note_LBBox::DrawThisAppearance(CFX_RenderDevice* pDevice,
                                         CFX_Matrix* pUser2Device) {
  CFX_FloatRect rcClient = GetClientRect();

  CFX_GraphStateData gsd;
  gsd.m_LineWidth = 1.0f;

  CFX_PathData pathCross;

  pathCross.SetPointCount(4);
  pathCross.SetPoint(0, rcClient.left, rcClient.top, FXPT_MOVETO);
  pathCross.SetPoint(1, rcClient.right, rcClient.bottom, FXPT_LINETO);
  pathCross.SetPoint(2, rcClient.left,
                     rcClient.bottom + rcClient.Height() * 0.5f, FXPT_MOVETO);
  pathCross.SetPoint(3, rcClient.left + rcClient.Width() * 0.5f,
                     rcClient.bottom, FXPT_LINETO);

  pDevice->DrawPath(
      &pathCross, pUser2Device, &gsd, 0,
      CPWL_Utils::PWLColorToFXColor(GetTextColor(), GetTransparency()),
      FXFILL_ALTERNATE);
}

CPWL_Note_RBBox::CPWL_Note_RBBox() {}

CPWL_Note_RBBox::~CPWL_Note_RBBox() {}

void CPWL_Note_RBBox::DrawThisAppearance(CFX_RenderDevice* pDevice,
                                         CFX_Matrix* pUser2Device) {
  CFX_FloatRect rcClient = GetClientRect();

  CFX_GraphStateData gsd;
  gsd.m_LineWidth = 1.0f;

  CFX_PathData pathCross;

  pathCross.SetPointCount(4);
  pathCross.SetPoint(0, rcClient.right, rcClient.top, FXPT_MOVETO);
  pathCross.SetPoint(1, rcClient.left, rcClient.bottom, FXPT_LINETO);
  pathCross.SetPoint(2, rcClient.right,
                     rcClient.bottom + rcClient.Height() * 0.5f, FXPT_MOVETO);
  pathCross.SetPoint(3, rcClient.left + rcClient.Width() * 0.5f,
                     rcClient.bottom, FXPT_LINETO);

  pDevice->DrawPath(
      &pathCross, pUser2Device, &gsd, 0,
      CPWL_Utils::PWLColorToFXColor(GetTextColor(), GetTransparency()),
      FXFILL_ALTERNATE);
}

CPWL_Note_Icon::CPWL_Note_Icon() : m_nType(0) {}

CPWL_Note_Icon::~CPWL_Note_Icon() {}

void CPWL_Note_Icon::SetIconType(int32_t nType) {
  m_nType = nType;
}

void CPWL_Note_Icon::DrawThisAppearance(CFX_RenderDevice* pDevice,
                                        CFX_Matrix* pUser2Device) {
  CPWL_Utils::DrawIconAppStream(pDevice, pUser2Device, m_nType, GetClientRect(),
                                GetBackgroundColor(), PWL_DEFAULT_BLACKCOLOR,
                                GetTransparency());
}

CPWL_Note_CloseBox::CPWL_Note_CloseBox() : m_bMouseDown(FALSE) {}

CPWL_Note_CloseBox::~CPWL_Note_CloseBox() {}

void CPWL_Note_CloseBox::DrawThisAppearance(CFX_RenderDevice* pDevice,
                                            CFX_Matrix* pUser2Device) {
  CPWL_Button::DrawThisAppearance(pDevice, pUser2Device);

  CFX_FloatRect rcClient = GetClientRect();
  rcClient = CPWL_Utils::DeflateRect(rcClient, 2.0f);

  CFX_GraphStateData gsd;
  gsd.m_LineWidth = 1.0f;

  CFX_PathData pathCross;

  if (m_bMouseDown) {
    rcClient.left += 0.5f;
    rcClient.right += 0.5f;
    rcClient.top -= 0.5f;
    rcClient.bottom -= 0.5f;
  }

  pathCross.SetPointCount(4);
  pathCross.SetPoint(0, rcClient.left, rcClient.bottom, FXPT_MOVETO);
  pathCross.SetPoint(1, rcClient.right, rcClient.top, FXPT_LINETO);
  pathCross.SetPoint(2, rcClient.left, rcClient.top, FXPT_MOVETO);
  pathCross.SetPoint(3, rcClient.right, rcClient.bottom, FXPT_LINETO);

  pDevice->DrawPath(
      &pathCross, pUser2Device, &gsd, 0,
      CPWL_Utils::PWLColorToFXColor(GetTextColor(), GetTransparency()),
      FXFILL_ALTERNATE);
}

FX_BOOL CPWL_Note_CloseBox::OnLButtonDown(const CFX_FloatPoint& point,
                                          FX_DWORD nFlag) {
  SetBorderStyle(PBS_INSET);
  InvalidateRect(NULL);

  m_bMouseDown = TRUE;

  return CPWL_Button::OnLButtonDown(point, nFlag);
}

FX_BOOL CPWL_Note_CloseBox::OnLButtonUp(const CFX_FloatPoint& point,
                                        FX_DWORD nFlag) {
  m_bMouseDown = FALSE;

  SetBorderStyle(PBS_BEVELED);
  InvalidateRect(NULL);

  return CPWL_Button::OnLButtonUp(point, nFlag);
}

CPWL_Note_Contents::CPWL_Note_Contents() : m_pEdit(NULL) {}

CPWL_Note_Contents::~CPWL_Note_Contents() {}

CFX_ByteString CPWL_Note_Contents::GetClassName() const {
  return "CPWL_Note_Contents";
}

void CPWL_Note_Contents::CreateChildWnd(const PWL_CREATEPARAM& cp) {
  m_pEdit = new CPWL_Note_Edit;
  PWL_CREATEPARAM ecp = cp;
  ecp.pParentWnd = this;
  ecp.dwFlags = PWS_VISIBLE | PWS_CHILD | PES_MULTILINE | PES_AUTORETURN |
                PES_TEXTOVERFLOW | PES_UNDO | PES_SPELLCHECK;

  m_pEdit->EnableNotify(FALSE);
  m_pEdit->Create(ecp);
  m_pEdit->EnableNotify(TRUE);
}

void CPWL_Note_Contents::SetText(const CFX_WideString& sText) {
  if (m_pEdit) {
    m_pEdit->EnableNotify(FALSE);
    m_pEdit->SetText(sText.c_str());
    m_pEdit->EnableNotify(TRUE);
    OnNotify(m_pEdit, PNM_NOTEEDITCHANGED, 0, 0);
  }
}

CFX_WideString CPWL_Note_Contents::GetText() const {
  if (m_pEdit)
    return m_pEdit->GetText();

  return L"";
}

CPWL_NoteItem* CPWL_Note_Contents::CreateSubItem() {
  CPWL_NoteItem* pNoteItem = new CPWL_NoteItem;
  PWL_CREATEPARAM icp = GetCreationParam();
  icp.pParentWnd = this;
  icp.dwFlags = PWS_CHILD | PWS_VISIBLE | PWS_BACKGROUND;
  pNoteItem->Create(icp);

  pNoteItem->OnCreateNoteItem();

  pNoteItem->ResetSubjectName(m_aChildren.GetSize() - 1);

  FX_SYSTEMTIME st;
  if (IFX_SystemHandler* pSH = GetSystemHandler())
    st = pSH->GetLocalTime();
  pNoteItem->SetDateTime(st);

  pNoteItem->SetContents(L"");

  OnNotify(pNoteItem, PNM_NOTEEDITCHANGED, 0, 0);

  return pNoteItem;
}

int32_t CPWL_Note_Contents::CountSubItems() const {
  return m_aChildren.GetSize() - 1;
}

IPWL_NoteItem* CPWL_Note_Contents::GetSubItems(int32_t index) const {
  int32_t nIndex = index + 1;

  if (nIndex > 0 && nIndex < m_aChildren.GetSize()) {
    if (CPWL_Wnd* pChild = m_aChildren.GetAt(nIndex)) {
      ASSERT(pChild->GetClassName() == "CPWL_NoteItem");
      CPWL_NoteItem* pItem = (CPWL_NoteItem*)pChild;
      return pItem;
    }
  }
  return NULL;
}

void CPWL_Note_Contents::DeleteSubItem(IPWL_NoteItem* pNoteItem) {
  int32_t nIndex = GetItemIndex((CPWL_NoteItem*)pNoteItem);

  if (nIndex > 0) {
    if (CPWL_NoteItem* pPWLNoteItem = (CPWL_NoteItem*)pNoteItem) {
      pPWLNoteItem->KillFocus();
      pPWLNoteItem->Destroy();
      delete pPWLNoteItem;
    }

    for (int32_t i = nIndex, sz = m_aChildren.GetSize(); i < sz; i++) {
      if (CPWL_Wnd* pChild = m_aChildren.GetAt(i)) {
        ASSERT(pChild->GetClassName() == "CPWL_NoteItem");
        CPWL_NoteItem* pItem = (CPWL_NoteItem*)pChild;
        pItem->ResetSubjectName(i);
      }
    }

    OnNotify(this, PNM_NOTEEDITCHANGED, 0, 0);
  }
}

IPWL_NoteItem* CPWL_Note_Contents::GetHitNoteItem(const CFX_FloatPoint& point) {
  CFX_FloatPoint pt = ParentToChild(point);

  for (int32_t i = 0, sz = m_aChildren.GetSize(); i < sz; i++) {
    if (CPWL_Wnd* pChild = m_aChildren.GetAt(i)) {
      if (pChild->GetClassName() == "CPWL_NoteItem") {
        CPWL_NoteItem* pNoteItem = (CPWL_NoteItem*)pChild;
        if (IPWL_NoteItem* pRet = pNoteItem->GetHitNoteItem(pt))
          return pRet;
      }
    }
  }
  return NULL;
}

void CPWL_Note_Contents::OnNotify(CPWL_Wnd* pWnd,
                                  FX_DWORD msg,
                                  intptr_t wParam,
                                  intptr_t lParam) {
  switch (msg) {
    case PNM_NOTEEDITCHANGED: {
      int32_t nIndex = GetItemIndex(pWnd);
      if (nIndex < 0)
        nIndex = 0;

      m_pEdit->EnableNotify(FALSE);
      ResetContent(nIndex);
      m_pEdit->EnableNotify(TRUE);

      for (int32_t i = nIndex + 1, sz = m_aChildren.GetSize(); i < sz; i++) {
        if (CPWL_Wnd* pChild = m_aChildren.GetAt(i))
          pChild->OnNotify(this, PNM_NOTERESET, 0, 0);
      }

      if (CPWL_Wnd* pParent = GetParentWindow()) {
        pParent->OnNotify(this, PNM_NOTEEDITCHANGED, 0, 0);
      }
    }
      return;
    case PNM_SCROLLWINDOW:
      SetScrollPos(CFX_FloatPoint(0.0f, *(FX_FLOAT*)lParam));
      ResetFace();
      InvalidateRect(NULL);
      return;
    case PNM_SETCARETINFO:
      if (PWL_CARET_INFO* pInfo = (PWL_CARET_INFO*)wParam) {
        PWL_CARET_INFO newInfo = *pInfo;
        newInfo.bVisible = TRUE;
        newInfo.ptHead = ChildToParent(pInfo->ptHead);
        newInfo.ptFoot = ChildToParent(pInfo->ptFoot);

        if (CPWL_Wnd* pParent = GetParentWindow()) {
          pParent->OnNotify(this, PNM_SETCARETINFO, (intptr_t)&newInfo, 0);
        }
      }
      return;
    case PNM_NOTERESET: {
      m_pEdit->EnableNotify(FALSE);
      ResetContent(0);
      m_pEdit->EnableNotify(TRUE);

      for (int32_t i = 1, sz = m_aChildren.GetSize(); i < sz; i++) {
        if (CPWL_Wnd* pChild = m_aChildren.GetAt(i))
          pChild->OnNotify(this, PNM_NOTERESET, 0, 0);
      }

      m_pEdit->EnableNotify(FALSE);
      ResetContent(0);
      m_pEdit->EnableNotify(TRUE);
    }
      return;
  }

  CPWL_Wnd::OnNotify(pWnd, msg, wParam, lParam);
}

FX_BOOL CPWL_Note_Contents::OnLButtonDown(const CFX_FloatPoint& point,
                                          FX_DWORD nFlag) {
  if (CPWL_Wnd::OnLButtonDown(point, nFlag))
    return TRUE;

  if (!m_pEdit->IsFocused()) {
    m_pEdit->SetFocus();
  }

  return TRUE;
}

void CPWL_Note_Contents::SetEditFocus(FX_BOOL bLast) {
  if (!m_pEdit->IsFocused()) {
    m_pEdit->SetFocus();
    m_pEdit->SetCaret(bLast ? m_pEdit->GetTotalWords() : 0);
  }
}

CPWL_Edit* CPWL_Note_Contents::GetEdit() const {
  return m_pEdit;
}

void CPWL_Note_Contents::EnableModify(FX_BOOL bEnabled) {
  if (!bEnabled)
    m_pEdit->AddFlag(PWS_READONLY);
  else
    m_pEdit->RemoveFlag(PWS_READONLY);

  for (int32_t i = 0, sz = m_aChildren.GetSize(); i < sz; i++) {
    if (CPWL_Wnd* pChild = m_aChildren.GetAt(i)) {
      if (pChild->GetClassName() == "CPWL_NoteItem") {
        CPWL_NoteItem* pNoteItem = (CPWL_NoteItem*)pChild;
        pNoteItem->EnableModify(bEnabled);
      }
    }
  }
}

void CPWL_Note_Contents::EnableRead(FX_BOOL bEnabled) {
  if (!bEnabled)
    m_pEdit->AddFlag(PES_NOREAD);
  else
    m_pEdit->RemoveFlag(PES_NOREAD);

  for (int32_t i = 0, sz = m_aChildren.GetSize(); i < sz; i++) {
    if (CPWL_Wnd* pChild = m_aChildren.GetAt(i)) {
      if (pChild->GetClassName() == "CPWL_NoteItem") {
        CPWL_NoteItem* pNoteItem = (CPWL_NoteItem*)pChild;
        pNoteItem->EnableRead(bEnabled);
      }
    }
  }
}

CPWL_NoteItem::CPWL_NoteItem()
    : m_pSubject(NULL),
      m_pDateTime(NULL),
      m_pContents(NULL),
      m_pPrivateData(NULL),
      m_sAuthor(L""),
      m_fOldItemHeight(0.0f),
      m_bSizeChanged(FALSE),
      m_bAllowModify(TRUE) {}

CPWL_NoteItem::~CPWL_NoteItem() {}

CFX_ByteString CPWL_NoteItem::GetClassName() const {
  return "CPWL_NoteItem";
}

void CPWL_NoteItem::CreateChildWnd(const PWL_CREATEPARAM& cp) {
  CPWL_Color sTextColor;

  if (CPWL_Utils::IsBlackOrWhite(GetBackgroundColor()))
    sTextColor = PWL_DEFAULT_WHITECOLOR;
  else
    sTextColor = PWL_DEFAULT_BLACKCOLOR;

  m_pSubject = new CPWL_Label;
  PWL_CREATEPARAM scp = cp;
  scp.pParentWnd = this;
  scp.dwFlags = PWS_VISIBLE | PWS_CHILD | PES_LEFT | PES_TOP;
  scp.sTextColor = sTextColor;
  m_pSubject->Create(scp);

  m_pDateTime = new CPWL_Label;
  PWL_CREATEPARAM dcp = cp;
  dcp.pParentWnd = this;
  dcp.dwFlags = PWS_VISIBLE | PWS_CHILD | PES_RIGHT | PES_TOP;
  dcp.sTextColor = sTextColor;
  m_pDateTime->Create(dcp);

  m_pContents = new CPWL_Note_Contents;
  PWL_CREATEPARAM ccp = cp;
  ccp.pParentWnd = this;
  ccp.sBackgroundColor =
      CPWL_Color(COLORTYPE_RGB, 240 / 255.0f, 240 / 255.0f, 240 / 255.0f);
  ccp.dwFlags = PWS_VISIBLE | PWS_CHILD | PWS_BACKGROUND;
  m_pContents->Create(ccp);
  m_pContents->SetItemSpace(POPUP_ITEM_SPACE);
  m_pContents->SetTopSpace(POPUP_ITEM_SPACE);
  m_pContents->SetBottomSpace(POPUP_ITEM_SPACE);
}

void CPWL_NoteItem::RePosChildWnd() {
  if (IsValid()) {
    CFX_FloatRect rcClient = GetClientRect();

    CFX_FloatRect rcSubject = rcClient;
    rcSubject.left += POPUP_ITEM_TEXT_INDENT;
    rcSubject.top = rcClient.top;
    rcSubject.right =
        PWL_MIN(rcSubject.left + m_pSubject->GetContentRect().Width() + 1.0f,
                rcClient.right);
    rcSubject.bottom = rcSubject.top - m_pSubject->GetContentRect().Height();
    rcSubject.Normalize();
    m_pSubject->Move(rcSubject, TRUE, FALSE);
    m_pSubject->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcSubject));

    CFX_FloatRect rcDate = rcClient;
    rcDate.right -= POPUP_ITEM_TEXT_INDENT;
    rcDate.left =
        PWL_MAX(rcDate.right - m_pDateTime->GetContentRect().Width() - 1.0f,
                rcSubject.right);
    rcDate.bottom = rcDate.top - m_pDateTime->GetContentRect().Height();
    rcDate.Normalize();
    m_pDateTime->Move(rcDate, TRUE, FALSE);
    m_pDateTime->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcDate));

    CFX_FloatRect rcContents = rcClient;
    rcContents.left += 1.0f;
    rcContents.right -= 1.0f;
    rcContents.top = rcDate.bottom - POPUP_ITEM_HEAD_BOTTOM;
    rcContents.bottom += POPUP_ITEM_BOTTOMWIDTH;
    rcContents.Normalize();
    m_pContents->Move(rcContents, TRUE, FALSE);
    m_pContents->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcContents));
  }

  SetClipRect(CPWL_Utils::InflateRect(GetWindowRect(), 1.0f));
}

void CPWL_NoteItem::SetPrivateData(void* pData) {
  m_pPrivateData = pData;
}

void CPWL_NoteItem::SetBkColor(const CPWL_Color& color) {
  CPWL_Color sBK = color;
  SetBackgroundColor(sBK);

  CPWL_Color sTextColor;

  if (CPWL_Utils::IsBlackOrWhite(sBK))
    sTextColor = PWL_DEFAULT_WHITECOLOR;
  else
    sTextColor = PWL_DEFAULT_BLACKCOLOR;

  SetTextColor(sTextColor);
  if (m_pSubject)
    m_pSubject->SetTextColor(sTextColor);
  if (m_pDateTime)
    m_pDateTime->SetTextColor(sTextColor);

  InvalidateRect(nullptr);

  if (IPWL_NoteNotify* pNotify = GetNoteNotify()) {
    pNotify->OnSetBkColor(this);
  }
}

void CPWL_NoteItem::SetSubjectName(const CFX_WideString& sName) {
  if (m_pSubject) {
    m_pSubject->SetText(sName.c_str());
  }

  if (IPWL_NoteNotify* pNotify = GetNoteNotify()) {
    pNotify->OnSetSubjectName(this);
  }
}

void CPWL_NoteItem::SetAuthorName(const CFX_WideString& sName) {
  m_sAuthor = sName;
  ResetSubjectName(-1);

  if (IPWL_NoteNotify* pNotify = GetNoteNotify()) {
    pNotify->OnSetAuthorName(this);
  }
}

void CPWL_NoteItem::ResetSubjectName(int32_t nItemIndex) {
  if (nItemIndex < 0) {
    if (CPWL_Wnd* pParent = GetParentWindow()) {
      ASSERT(pParent->GetClassName() == "CPWL_Note_Contents");

      CPWL_Note_Contents* pContents = (CPWL_Note_Contents*)pParent;
      nItemIndex = pContents->GetItemIndex(this);
    }
  }

  const CPWL_Note* pNote = GetNote();
  CFX_WideString sSubject;
  sSubject.Format(pNote->GetReplyString().c_str(), nItemIndex);

  if (!m_sAuthor.IsEmpty()) {
    sSubject += L" - ";
    sSubject += m_sAuthor;
  }
  SetSubjectName(sSubject);
  RePosChildWnd();
}

void CPWL_NoteItem::SetDateTime(FX_SYSTEMTIME time) {
  m_dtNote = time;

  CFX_WideString swTime;
  swTime.Format(L"%04d-%02d-%02d %02d:%02d:%02d", time.wYear, time.wMonth,
                time.wDay, time.wHour, time.wMinute, time.wSecond);
  if (m_pDateTime) {
    m_pDateTime->SetText(swTime.c_str());
  }

  RePosChildWnd();

  if (IPWL_NoteNotify* pNotify = GetNoteNotify()) {
    pNotify->OnSetDateTime(this);
  }
}

void CPWL_NoteItem::SetContents(const CFX_WideString& sContents) {
  if (m_pContents) {
    m_pContents->SetText(sContents);
  }

  if (IPWL_NoteNotify* pNotify = GetNoteNotify()) {
    pNotify->OnSetContents(this);
  }
}

CPWL_NoteItem* CPWL_NoteItem::GetParentNoteItem() const {
  if (CPWL_Wnd* pParent = GetParentWindow()) {
    if (CPWL_Wnd* pGrand = pParent->GetParentWindow()) {
      ASSERT(pGrand->GetClassName() == "CPWL_NoteItem");
      return (CPWL_NoteItem*)pGrand;
    }
  }

  return NULL;
}

IPWL_NoteItem* CPWL_NoteItem::GetParentItem() const {
  return GetParentNoteItem();
}

CPWL_Edit* CPWL_NoteItem::GetEdit() const {
  if (m_pContents)
    return m_pContents->GetEdit();
  return NULL;
}

void* CPWL_NoteItem::GetPrivateData() const {
  return m_pPrivateData;
}

CFX_WideString CPWL_NoteItem::GetAuthorName() const {
  return m_sAuthor;
}

CPWL_Color CPWL_NoteItem::GetBkColor() const {
  return GetBackgroundColor();
}

CFX_WideString CPWL_NoteItem::GetContents() const {
  if (m_pContents)
    return m_pContents->GetText();

  return L"";
}

FX_SYSTEMTIME CPWL_NoteItem::GetDateTime() const {
  return m_dtNote;
}

CFX_WideString CPWL_NoteItem::GetSubjectName() const {
  if (m_pSubject)
    return m_pSubject->GetText();

  return L"";
}

CPWL_NoteItem* CPWL_NoteItem::CreateNoteItem() {
  if (m_pContents)
    return m_pContents->CreateSubItem();

  return NULL;
}

IPWL_NoteItem* CPWL_NoteItem::CreateSubItem() {
  return CreateNoteItem();
}

int32_t CPWL_NoteItem::CountSubItems() const {
  if (m_pContents)
    return m_pContents->CountSubItems();

  return 0;
}

IPWL_NoteItem* CPWL_NoteItem::GetSubItems(int32_t index) const {
  if (m_pContents)
    return m_pContents->GetSubItems(index);

  return NULL;
}

void CPWL_NoteItem::DeleteSubItem(IPWL_NoteItem* pNoteItem) {
  KillFocus();

  if (IPWL_NoteNotify* pNotify = GetNoteNotify()) {
    pNotify->OnItemDelete(pNoteItem);
  }

  if (m_pContents)
    m_pContents->DeleteSubItem(pNoteItem);
}

IPWL_NoteItem* CPWL_NoteItem::GetHitNoteItem(const CFX_FloatPoint& point) {
  CFX_FloatPoint pt = ParentToChild(point);

  if (WndHitTest(pt)) {
    if (m_pContents) {
      if (IPWL_NoteItem* pNoteItem = m_pContents->GetHitNoteItem(pt))
        return pNoteItem;
    }

    return this;
  }

  return NULL;
}

IPWL_NoteItem* CPWL_NoteItem::GetFocusedNoteItem() const {
  if (const CPWL_Wnd* pWnd = GetFocused()) {
    if (pWnd->GetClassName() == "CPWL_Edit") {
      if (CPWL_Wnd* pParent = pWnd->GetParentWindow()) {
        ASSERT(pParent->GetClassName() == "CPWL_Note_Contents");

        if (CPWL_Wnd* pGrand = pParent->GetParentWindow()) {
          ASSERT(pGrand->GetClassName() == "CPWL_NoteItem");
          return (CPWL_NoteItem*)pGrand;
        }
      }
    }
  }

  return NULL;
}

FX_FLOAT CPWL_NoteItem::GetItemHeight(FX_FLOAT fLimitWidth) {
  if (fLimitWidth > 0) {
    if (!m_bSizeChanged)
      return m_fOldItemHeight;

    m_bSizeChanged = FALSE;

    FX_FLOAT fRet = m_pDateTime->GetContentRect().Height();
    FX_FLOAT fBorderWidth = (FX_FLOAT)GetBorderWidth();
    if (fLimitWidth > fBorderWidth * 2)
      fRet += m_pContents->GetContentsHeight(fLimitWidth - fBorderWidth * 2);
    fRet += POPUP_ITEM_HEAD_BOTTOM + POPUP_ITEM_BOTTOMWIDTH + fBorderWidth * 2;

    return m_fOldItemHeight = fRet;
  }

  return 0;
}

FX_FLOAT CPWL_NoteItem::GetItemLeftMargin() {
  return POPUP_ITEM_SIDEMARGIN;
}

FX_FLOAT CPWL_NoteItem::GetItemRightMargin() {
  return POPUP_ITEM_SIDEMARGIN;
}

FX_BOOL CPWL_NoteItem::OnLButtonDown(const CFX_FloatPoint& point,
                                     FX_DWORD nFlag) {
  if (!m_pContents->WndHitTest(m_pContents->ParentToChild(point))) {
    SetNoteFocus(FALSE);
  }

  CPWL_Wnd::OnLButtonDown(point, nFlag);

  return TRUE;
}

FX_BOOL CPWL_NoteItem::OnRButtonUp(const CFX_FloatPoint& point,
                                   FX_DWORD nFlag) {
  if (!m_pContents->WndHitTest(m_pContents->ParentToChild(point))) {
    SetNoteFocus(FALSE);
    PopupNoteItemMenu(point);

    return TRUE;
  }

  return CPWL_Wnd::OnRButtonUp(point, nFlag);
}

void CPWL_NoteItem::OnNotify(CPWL_Wnd* pWnd,
                             FX_DWORD msg,
                             intptr_t wParam,
                             intptr_t lParam) {
  switch (msg) {
    case PNM_NOTEEDITCHANGED:
      m_bSizeChanged = TRUE;

      if (CPWL_Wnd* pParent = GetParentWindow()) {
        pParent->OnNotify(this, PNM_NOTEEDITCHANGED, 0, 0);
      }
      return;
    case PNM_SETCARETINFO:
      if (PWL_CARET_INFO* pInfo = (PWL_CARET_INFO*)wParam) {
        PWL_CARET_INFO newInfo = *pInfo;
        newInfo.bVisible = TRUE;
        newInfo.ptHead = ChildToParent(pInfo->ptHead);
        newInfo.ptFoot = ChildToParent(pInfo->ptFoot);

        if (CPWL_Wnd* pParent = GetParentWindow()) {
          pParent->OnNotify(this, PNM_SETCARETINFO, (intptr_t)&newInfo, 0);
        }
      }
      return;
    case PNM_NOTERESET:
      m_bSizeChanged = TRUE;
      m_pContents->OnNotify(this, PNM_NOTERESET, 0, 0);

      return;
  }

  CPWL_Wnd::OnNotify(pWnd, msg, wParam, lParam);
}

void CPWL_NoteItem::PopupNoteItemMenu(const CFX_FloatPoint& point) {
  if (IPWL_NoteNotify* pNotify = GetNoteNotify()) {
    int32_t x, y;
    PWLtoWnd(point, x, y);
    if (IFX_SystemHandler* pSH = GetSystemHandler())
      pSH->ClientToScreen(GetAttachedHWnd(), x, y);
    pNotify->OnPopupMenu(this, x, y);
  }
}

const CPWL_Note* CPWL_NoteItem::GetNote() const {
  if (const CPWL_Wnd* pRoot = GetRootWnd()) {
    ASSERT(pRoot->GetClassName() == "CPWL_NoteItem");
    CPWL_NoteItem* pNoteItem = (CPWL_NoteItem*)pRoot;
    if (pNoteItem->IsTopItem()) {
      return (CPWL_Note*)pNoteItem;
    }
  }

  return NULL;
}

IPWL_NoteNotify* CPWL_NoteItem::GetNoteNotify() const {
  if (const CPWL_Note* pNote = GetNote())
    return pNote->GetNoteNotify();

  return NULL;
}

void CPWL_NoteItem::OnCreateNoteItem() {
  if (IPWL_NoteNotify* pNotify = GetNoteNotify()) {
    pNotify->OnItemCreate(this);
  }
}

void CPWL_NoteItem::OnContentsValidate() {
  if (IPWL_NoteNotify* pNotify = GetNoteNotify()) {
    pNotify->OnSetContents(this);
  }
}

void CPWL_NoteItem::SetNoteFocus(FX_BOOL bLast) {
  m_pContents->SetEditFocus(bLast);
}

void CPWL_NoteItem::EnableModify(FX_BOOL bEnabled) {
  m_pContents->EnableModify(bEnabled);
  m_bAllowModify = bEnabled;
}

void CPWL_NoteItem::EnableRead(FX_BOOL bEnabled) {
  m_pContents->EnableRead(bEnabled);
}

CPWL_Note::CPWL_Note(IPopup_Note* pPopupNote,
                     IPWL_NoteNotify* pNoteNotify,
                     IPWL_NoteHandler* pNoteHandler)
    : m_pAuthor(NULL),
      m_pIcon(NULL),
      m_pCloseBox(NULL),
      m_pLBBox(NULL),
      m_pRBBox(NULL),
      m_pContentsBar(NULL),
      m_pOptions(NULL),
      m_pNoteNotify(pNoteNotify),
      m_bResizing(FALSE),
      m_bEnableNotify(TRUE) {}

CPWL_Note::~CPWL_Note() {}

IPWL_NoteItem* CPWL_Note::Reply() {
  return CreateNoteItem();
}

void CPWL_Note::EnableNotify(FX_BOOL bEnabled) {
  m_bEnableNotify = bEnabled;
}

void CPWL_Note::RePosChildWnd() {
  RePosNoteChildren();
  m_pContents->OnNotify(this, PNM_NOTERESET, 0, 0);
  ResetScrollBar();
  m_pContents->OnNotify(this, PNM_NOTERESET, 0, 0);
  OnNotify(this, PNM_NOTEEDITCHANGED, 0, 0);
  if (const CPWL_Wnd* pWnd = GetFocused()) {
    if (pWnd->GetClassName() == "CPWL_Edit") {
      CPWL_Edit* pEdit = (CPWL_Edit*)pWnd;
      pEdit->SetCaret(pEdit->GetCaret());
    }
  }
}

FX_BOOL CPWL_Note::ResetScrollBar() {
  FX_BOOL bScrollChanged = FALSE;

  if (ScrollBarShouldVisible()) {
    if (!m_pContentsBar->IsVisible()) {
      m_pContentsBar->SetVisible(TRUE);
      if (m_pContentsBar->IsVisible()) {
        m_pContentsBar->InvalidateRect(NULL);
        bScrollChanged = TRUE;
      }
    }
  } else {
    if (m_pContentsBar->IsVisible()) {
      m_pContentsBar->SetVisible(FALSE);
      m_pContentsBar->InvalidateRect(NULL);

      bScrollChanged = TRUE;
    }
  }

  if (bScrollChanged) {
    CFX_FloatRect rcNote = GetClientRect();
    CFX_FloatRect rcContents = m_pContents->GetWindowRect();
    rcContents.right = rcNote.right - 3.0f;
    if (m_pContentsBar->IsVisible())
      rcContents.right -= PWL_SCROLLBAR_WIDTH;
    m_pContents->Move(rcContents, TRUE, TRUE);
    m_pContents->SetScrollPos(CFX_FloatPoint(0.0f, 0.0f));
    m_pContents->InvalidateRect(NULL);
  }

  return bScrollChanged;
}

FX_BOOL CPWL_Note::ScrollBarShouldVisible() {
  CFX_FloatRect rcContentsFact = m_pContents->GetScrollArea();
  CFX_FloatRect rcContentsClient = m_pContents->GetClientRect();

  return rcContentsFact.Height() > rcContentsClient.Height();
}

void CPWL_Note::SetOptionsText(const CFX_WideString& sText) {
  if (m_pOptions)
    m_pOptions->SetText(sText);

  RePosNoteChildren();
}

void CPWL_Note::RePosNoteChildren() {
  if (m_bResizing)
    return;

  m_bResizing = TRUE;

  if (IsValid()) {
    CFX_FloatRect rcClient = GetClientRect();

    CFX_FloatRect rcIcon = rcClient;
    rcIcon.top -= 2.0f;
    rcIcon.right = rcIcon.left + 14.0f;
    rcIcon.bottom = rcIcon.top - 14.0f;
    rcIcon.Normalize();
    m_pIcon->Move(rcIcon, TRUE, FALSE);
    m_pIcon->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcIcon));

    CFX_FloatRect rcCloseBox = rcClient;
    rcCloseBox.right -= 1.0f;
    rcCloseBox.top -= 1.0f;
    rcCloseBox.left = rcCloseBox.right - 14.0f;
    rcCloseBox.bottom = rcCloseBox.top - 14.0f;
    rcCloseBox.Normalize();
    m_pCloseBox->Move(rcCloseBox, TRUE, FALSE);
    m_pCloseBox->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcCloseBox));

    CFX_FloatRect rcDate = rcClient;
    rcDate.right = rcCloseBox.left - POPUP_ITEM_TEXT_INDENT;
    rcDate.left =
        PWL_MAX(rcDate.right - m_pDateTime->GetContentRect().Width() - 1.0f,
                rcIcon.right + 1.0f);
    rcDate.top = rcClient.top - 2.0f;
    rcDate.bottom = rcDate.top - m_pDateTime->GetContentRect().Height();
    rcDate.Normalize();
    m_pDateTime->Move(rcDate, TRUE, FALSE);
    m_pDateTime->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcDate));

    CFX_FloatRect rcSubject = rcClient;
    rcSubject.top = rcClient.top - 2.0f;
    rcSubject.left = rcIcon.right + POPUP_ITEM_TEXT_INDENT;
    rcSubject.right =
        PWL_MIN(rcSubject.left + m_pSubject->GetContentRect().Width() + 1.0f,
                rcDate.left - 1.0f);
    rcSubject.bottom = rcSubject.top - m_pSubject->GetContentRect().Height();
    rcSubject.Normalize();
    m_pSubject->Move(rcSubject, TRUE, FALSE);
    m_pSubject->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcSubject));

    CFX_FloatRect rcOptions = rcClient;
    rcOptions.left =
        PWL_MAX(rcOptions.right - m_pOptions->GetContentRect().Width(),
                rcIcon.right + 1.0f);
    rcOptions.top = rcSubject.bottom - 4.0f;
    rcOptions.bottom = rcOptions.top - m_pOptions->GetContentRect().Height();
    rcOptions.Normalize();
    m_pOptions->Move(rcOptions, TRUE, FALSE);
    m_pOptions->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcOptions));

    CFX_FloatRect rcAuthor = rcClient;
    rcAuthor.top = rcSubject.bottom - 4.0f;
    rcAuthor.left = rcSubject.left;
    rcAuthor.right =
        PWL_MIN(rcSubject.left + m_pAuthor->GetContentRect().Width() + 1.0f,
                rcOptions.left - 1.0f);
    rcAuthor.bottom = rcAuthor.top - m_pAuthor->GetContentRect().Height();
    rcAuthor.Normalize();
    m_pAuthor->Move(rcAuthor, TRUE, FALSE);
    m_pAuthor->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcAuthor));

    CFX_FloatRect rcLBBox = rcClient;
    rcLBBox.top = rcLBBox.bottom + 7.0f;
    rcLBBox.right = rcLBBox.left + 7.0f;
    rcLBBox.Normalize();
    m_pLBBox->Move(rcLBBox, TRUE, FALSE);
    m_pLBBox->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcLBBox));

    CFX_FloatRect rcRBBox = rcClient;
    rcRBBox.top = rcRBBox.bottom + 7.0f;
    rcRBBox.left = rcRBBox.right - 7.0f;
    rcRBBox.Normalize();
    m_pRBBox->Move(rcRBBox, TRUE, FALSE);
    m_pRBBox->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcRBBox));

    CFX_FloatRect rcContents = rcClient;
    rcContents.top = rcAuthor.bottom - POPUP_ITEM_HEAD_BOTTOM;
    rcContents.left += 3.0f;
    rcContents.right -= 3.0f;
    if (m_pContentsBar->IsVisible())
      rcContents.right -= PWL_SCROLLBAR_WIDTH;
    rcContents.bottom += 14.0f;
    rcContents.Normalize();
    m_pContents->Move(rcContents, FALSE, FALSE);
    m_pContents->SetVisible(CPWL_Utils::ContainsRect(rcClient, rcContents));

    CFX_FloatRect rcContentsBar = rcContents;
    rcContentsBar.right = rcClient.right - 3.0f;
    rcContentsBar.left = rcContentsBar.right - PWL_SCROLLBAR_WIDTH;
    rcContentsBar.Normalize();
    m_pContentsBar->Move(rcContentsBar, TRUE, FALSE);
  }

  m_bResizing = FALSE;
}

void CPWL_Note::CreateChildWnd(const PWL_CREATEPARAM& cp) {
  CPWL_NoteItem::CreateChildWnd(cp);

  CPWL_Color sTextColor;

  if (CPWL_Utils::IsBlackOrWhite(GetBackgroundColor()))
    sTextColor = PWL_DEFAULT_WHITECOLOR;
  else
    sTextColor = PWL_DEFAULT_BLACKCOLOR;

  m_pAuthor = new CPWL_Label;
  PWL_CREATEPARAM acp = cp;
  acp.pParentWnd = this;
  acp.dwFlags = PWS_VISIBLE | PWS_CHILD | PES_LEFT | PES_TOP;
  acp.sTextColor = sTextColor;
  m_pAuthor->Create(acp);

  m_pCloseBox = new CPWL_Note_CloseBox;
  PWL_CREATEPARAM ccp = cp;
  ccp.pParentWnd = this;
  ccp.dwBorderWidth = 2;
  ccp.nBorderStyle = PBS_BEVELED;
  ccp.dwFlags = PWS_VISIBLE | PWS_CHILD | PWS_BORDER;
  ccp.sTextColor = sTextColor;
  m_pCloseBox->Create(ccp);

  m_pIcon = new CPWL_Note_Icon;
  PWL_CREATEPARAM icp = cp;
  icp.pParentWnd = this;
  icp.dwFlags = PWS_VISIBLE | PWS_CHILD;
  m_pIcon->Create(icp);

  m_pOptions = new CPWL_Note_Options;
  PWL_CREATEPARAM ocp = cp;
  ocp.pParentWnd = this;
  ocp.dwFlags = PWS_CHILD | PWS_VISIBLE;
  ocp.sTextColor = sTextColor;
  m_pOptions->Create(ocp);

  m_pLBBox = new CPWL_Note_LBBox;
  PWL_CREATEPARAM lcp = cp;
  lcp.pParentWnd = this;
  lcp.dwFlags = PWS_VISIBLE | PWS_CHILD;
  lcp.eCursorType = FXCT_NESW;
  lcp.sTextColor = sTextColor;
  m_pLBBox->Create(lcp);

  m_pRBBox = new CPWL_Note_RBBox;
  PWL_CREATEPARAM rcp = cp;
  rcp.pParentWnd = this;
  rcp.dwFlags = PWS_VISIBLE | PWS_CHILD;
  rcp.eCursorType = FXCT_NWSE;
  rcp.sTextColor = sTextColor;
  m_pRBBox->Create(rcp);

  m_pContentsBar = new CPWL_ScrollBar(SBT_VSCROLL);
  PWL_CREATEPARAM scp = cp;
  scp.pParentWnd = this;
  scp.sBackgroundColor =
      CPWL_Color(COLORTYPE_RGB, 240 / 255.0f, 240 / 255.0f, 240 / 255.0f);
  scp.dwFlags = PWS_CHILD | PWS_VISIBLE | PWS_BACKGROUND;
  m_pContentsBar->Create(scp);
  m_pContentsBar->SetNotifyForever(TRUE);
}

void CPWL_Note::SetSubjectName(const CFX_WideString& sName) {
  CPWL_NoteItem::SetSubjectName(sName);
  RePosChildWnd();
}

void CPWL_Note::SetAuthorName(const CFX_WideString& sName) {
  if (m_pAuthor) {
    m_pAuthor->SetText(sName.c_str());
    RePosChildWnd();
  }

  if (IPWL_NoteNotify* pNotify = GetNoteNotify()) {
    pNotify->OnSetAuthorName(this);
  }
}

CFX_WideString CPWL_Note::GetAuthorName() const {
  if (m_pAuthor)
    return m_pAuthor->GetText();

  return L"";
}

FX_BOOL CPWL_Note::OnMouseWheel(short zDelta,
                                const CFX_FloatPoint& point,
                                FX_DWORD nFlag) {
  CFX_FloatPoint ptScroll = m_pContents->GetScrollPos();
  CFX_FloatRect rcScroll = m_pContents->GetScrollArea();
  CFX_FloatRect rcContents = m_pContents->GetClientRect();

  if (rcScroll.top - rcScroll.bottom > rcContents.Height()) {
    CFX_FloatPoint ptNew = ptScroll;

    if (zDelta > 0)
      ptNew.y += 30;
    else
      ptNew.y -= 30;

    if (ptNew.y > rcScroll.top)
      ptNew.y = rcScroll.top;
    if (ptNew.y < rcScroll.bottom + rcContents.Height())
      ptNew.y = rcScroll.bottom + rcContents.Height();
    if (ptNew.y < rcScroll.bottom)
      ptNew.y = rcScroll.bottom;

    if (ptNew.y != ptScroll.y) {
      m_pContents->OnNotify(this, PNM_NOTERESET, 0, 0);
      m_pContents->OnNotify(this, PNM_SCROLLWINDOW, SBT_VSCROLL,
                            (intptr_t)&ptNew.y);
      m_pContentsBar->OnNotify(this, PNM_SETSCROLLPOS, SBT_VSCROLL,
                               (intptr_t)&ptNew.y);

      return TRUE;
    }
  }

  return FALSE;
}

void CPWL_Note::OnNotify(CPWL_Wnd* pWnd,
                         FX_DWORD msg,
                         intptr_t wParam,
                         intptr_t lParam) {
  switch (msg) {
    case PNM_NOTEEDITCHANGED: {
      CFX_FloatRect rcScroll = m_pContents->GetScrollArea();

      PWL_SCROLL_INFO sInfo;
      sInfo.fContentMin = rcScroll.bottom;
      sInfo.fContentMax = rcScroll.top;
      sInfo.fPlateWidth = m_pContents->GetClientRect().Height();
      sInfo.fSmallStep = 13.0f;
      sInfo.fBigStep = sInfo.fPlateWidth;

      if (FXSYS_memcmp(&m_OldScrollInfo, &sInfo, sizeof(PWL_SCROLL_INFO)) !=
          0) {
        FX_BOOL bScrollChanged = FALSE;

        if (lParam < 3) {
          bScrollChanged = ResetScrollBar();
          if (bScrollChanged) {
            lParam++;
            m_pContents->OnNotify(this, PNM_NOTERESET, 0, 0);
            OnNotify(this, PNM_NOTEEDITCHANGED, 0, lParam);
          }
        }

        if (!bScrollChanged) {
          if (m_pContentsBar->IsVisible()) {
            m_pContentsBar->OnNotify(pWnd, PNM_SETSCROLLINFO, SBT_VSCROLL,
                                     (intptr_t)&sInfo);
            m_OldScrollInfo = sInfo;

            CFX_FloatPoint ptScroll = m_pContents->GetScrollPos();
            CFX_FloatPoint ptOld = ptScroll;

            if (ptScroll.y > sInfo.fContentMax)
              ptScroll.y = sInfo.fContentMax;
            if (ptScroll.y < sInfo.fContentMin + sInfo.fPlateWidth)
              ptScroll.y = sInfo.fContentMin + sInfo.fPlateWidth;
            if (ptScroll.y < sInfo.fContentMin)
              ptScroll.y = sInfo.fContentMin;

            if (ptOld.y != ptScroll.y) {
              m_pContentsBar->OnNotify(this, PNM_SETSCROLLPOS, SBT_VSCROLL,
                                       (intptr_t)&ptScroll.y);
              m_pContentsBar->InvalidateRect(NULL);
              m_pContents->OnNotify(this, PNM_SCROLLWINDOW, SBT_VSCROLL,
                                    (intptr_t)&ptScroll.y);
            }
          }
        }
      }
    }

      m_pContents->InvalidateRect(NULL);

      return;
    case PNM_SCROLLWINDOW:
      if (m_pContents)
        m_pContents->OnNotify(pWnd, msg, wParam, lParam);
      return;
    case PNM_SETSCROLLPOS:
      if (m_pContentsBar)
        m_pContentsBar->OnNotify(pWnd, PNM_SETSCROLLPOS, wParam, lParam);
      return;
  }

  if (msg == PNM_SETCARETINFO && IsValid()) {
    if (PWL_CARET_INFO* pInfo = (PWL_CARET_INFO*)wParam) {
      if (m_pContents) {
        CFX_FloatRect rcClient = m_pContents->GetClientRect();
        if (pInfo->ptHead.y > rcClient.top) {
          CFX_FloatPoint pt = m_pContents->OutToIn(pInfo->ptHead);
          m_pContents->OnNotify(this, PNM_SCROLLWINDOW, SBT_VSCROLL,
                                (intptr_t)&pt.y);

          CFX_FloatPoint ptScroll = m_pContents->GetScrollPos();
          m_pContentsBar->OnNotify(this, PNM_SETSCROLLPOS, SBT_VSCROLL,
                                   (intptr_t)&ptScroll.y);

          return;
        }

        if (pInfo->ptFoot.y < rcClient.bottom) {
          CFX_FloatPoint pt = m_pContents->OutToIn(pInfo->ptFoot);
          pt.y += rcClient.Height();
          m_pContents->OnNotify(this, PNM_SCROLLWINDOW, SBT_VSCROLL,
                                (intptr_t)&pt.y);

          CFX_FloatPoint ptScroll = m_pContents->GetScrollPos();
          m_pContentsBar->OnNotify(this, PNM_SETSCROLLPOS, SBT_VSCROLL,
                                   (intptr_t)&ptScroll.y);

          return;
        }
      }
    }
  }

  CPWL_NoteItem::OnNotify(pWnd, msg, wParam, lParam);
}

void CPWL_Note::SetBkColor(const CPWL_Color& color) {
  CPWL_NoteItem::SetBkColor(color);

  CPWL_Color sBK = color;
  CPWL_Color sTextColor;
  if (CPWL_Utils::IsBlackOrWhite(sBK))
    sTextColor = PWL_DEFAULT_WHITECOLOR;
  else
    sTextColor = PWL_DEFAULT_BLACKCOLOR;

  if (m_pCloseBox)
    m_pCloseBox->SetTextColor(sTextColor);
  if (m_pAuthor)
    m_pAuthor->SetTextColor(sTextColor);
  if (m_pOptions)
    m_pOptions->SetTextColor(sTextColor);
  if (m_pLBBox)
    m_pLBBox->SetTextColor(sTextColor);
  if (m_pRBBox)
    m_pRBBox->SetTextColor(sTextColor);
}

FX_BOOL CPWL_Note::OnLButtonDown(const CFX_FloatPoint& point, FX_DWORD nFlag) {
  if (m_pOptions->WndHitTest(m_pOptions->ParentToChild(point))) {
    if (IPWL_NoteNotify* pNotify = GetNoteNotify()) {
      int32_t x, y;
      PWLtoWnd(point, x, y);
      if (IFX_SystemHandler* pSH = GetSystemHandler())
        pSH->ClientToScreen(GetAttachedHWnd(), x, y);
      KillFocus();
      pNotify->OnPopupMenu(x, y);

      return TRUE;
    }
  }

  return CPWL_Wnd::OnLButtonDown(point, nFlag);
}

FX_BOOL CPWL_Note::OnRButtonUp(const CFX_FloatPoint& point, FX_DWORD nFlag) {
  return CPWL_Wnd::OnRButtonUp(point, nFlag);
}

const CPWL_Note* CPWL_Note::GetNote() const {
  return this;
}

IPWL_NoteNotify* CPWL_Note::GetNoteNotify() const {
  return m_bEnableNotify ? m_pNoteNotify : nullptr;
}

void CPWL_Note::SetIconType(int32_t nType) {
  if (m_pIcon)
    m_pIcon->SetIconType(nType);
}

void CPWL_Note::EnableModify(FX_BOOL bEnabled) {
  m_pContents->EnableModify(bEnabled);
}

void CPWL_Note::EnableRead(FX_BOOL bEnabled) {
  m_pContents->EnableRead(bEnabled);
}

CFX_WideString CPWL_Note::GetReplyString() const {
  return m_sReplyString;
}

void CPWL_Note::SetReplyString(const CFX_WideString& str) {
  m_sReplyString = str;
}