xref: /aosp_15_r20/external/pdfium/xfa/fwl/cfwl_edit.cpp (revision 3ac0a46f773bac49fa9476ec2b1cf3f8da5ec3a4)
1 // Copyright 2014 The PDFium Authors
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 "xfa/fwl/cfwl_edit.h"
8 
9 #include <algorithm>
10 #include <memory>
11 #include <utility>
12 #include <vector>
13 
14 #include "build/build_config.h"
15 #include "core/fxge/cfx_renderdevice.h"
16 #include "core/fxge/text_char_pos.h"
17 #include "third_party/base/check.h"
18 #include "third_party/base/numerics/safe_conversions.h"
19 #include "v8/include/cppgc/visitor.h"
20 #include "xfa/fde/cfde_textout.h"
21 #include "xfa/fgas/font/cfgas_gefont.h"
22 #include "xfa/fgas/graphics/cfgas_gegraphics.h"
23 #include "xfa/fgas/graphics/cfgas_gepath.h"
24 #include "xfa/fwl/cfwl_app.h"
25 #include "xfa/fwl/cfwl_caret.h"
26 #include "xfa/fwl/cfwl_event.h"
27 #include "xfa/fwl/cfwl_eventtextwillchange.h"
28 #include "xfa/fwl/cfwl_eventvalidate.h"
29 #include "xfa/fwl/cfwl_messagekey.h"
30 #include "xfa/fwl/cfwl_messagemouse.h"
31 #include "xfa/fwl/cfwl_themebackground.h"
32 #include "xfa/fwl/cfwl_themepart.h"
33 #include "xfa/fwl/cfwl_widgetmgr.h"
34 #include "xfa/fwl/fwl_widgetdef.h"
35 #include "xfa/fwl/ifwl_themeprovider.h"
36 #include "xfa/fwl/theme/cfwl_utils.h"
37 
38 namespace {
39 
40 constexpr int kEditMargin = 3;
41 
42 #if BUILDFLAG(IS_APPLE)
43 constexpr XFA_FWL_KeyFlag kEditingModifier = XFA_FWL_KeyFlag::kCommand;
44 #else
45 constexpr XFA_FWL_KeyFlag kEditingModifier = XFA_FWL_KeyFlag::kCtrl;
46 #endif
47 
48 }  // namespace
49 
CFWL_Edit(CFWL_App * app,const Properties & properties,CFWL_Widget * pOuter)50 CFWL_Edit::CFWL_Edit(CFWL_App* app,
51                      const Properties& properties,
52                      CFWL_Widget* pOuter)
53     : CFWL_Widget(app, properties, pOuter),
54       m_pEditEngine(std::make_unique<CFDE_TextEditEngine>()) {
55   m_pEditEngine->SetDelegate(this);
56 }
57 
58 CFWL_Edit::~CFWL_Edit() = default;
59 
PreFinalize()60 void CFWL_Edit::PreFinalize() {
61   m_pEditEngine->SetDelegate(nullptr);
62   if (m_Properties.m_dwStates & FWL_STATE_WGT_Focused)
63     HideCaret(nullptr);
64   CFWL_Widget::PreFinalize();
65 }
66 
Trace(cppgc::Visitor * visitor) const67 void CFWL_Edit::Trace(cppgc::Visitor* visitor) const {
68   CFWL_Widget::Trace(visitor);
69   visitor->Trace(m_pVertScrollBar);
70   visitor->Trace(m_pCaret);
71 }
72 
GetClassID() const73 FWL_Type CFWL_Edit::GetClassID() const {
74   return FWL_Type::Edit;
75 }
76 
GetWidgetRect()77 CFX_RectF CFWL_Edit::GetWidgetRect() {
78   CFX_RectF rect = m_WidgetRect;
79   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_OuterScrollbar) {
80     float scrollbarWidth = GetThemeProvider()->GetScrollBarWidth();
81     if (IsShowVertScrollBar()) {
82       rect.width += scrollbarWidth;
83       rect.width += kEditMargin;
84     }
85   }
86   return rect;
87 }
88 
GetAutosizedWidgetRect()89 CFX_RectF CFWL_Edit::GetAutosizedWidgetRect() {
90   CFX_RectF rect;
91   if (m_pEditEngine->GetLength() > 0) {
92     CFX_SizeF size = CalcTextSize(
93         m_pEditEngine->GetText(),
94         !!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_MultiLine));
95     rect = CFX_RectF(0, 0, size);
96   }
97   InflateWidgetRect(rect);
98   return rect;
99 }
100 
SetStates(uint32_t dwStates)101 void CFWL_Edit::SetStates(uint32_t dwStates) {
102   if ((m_Properties.m_dwStates & FWL_STATE_WGT_Invisible) ||
103       (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)) {
104     HideCaret(nullptr);
105   }
106   CFWL_Widget::SetStates(dwStates);
107 }
108 
Update()109 void CFWL_Edit::Update() {
110   if (IsLocked())
111     return;
112 
113   Layout();
114   if (m_ClientRect.IsEmpty())
115     return;
116 
117   UpdateEditEngine();
118   UpdateVAlignment();
119   UpdateScroll();
120   InitCaret();
121 }
122 
HitTest(const CFX_PointF & point)123 FWL_WidgetHit CFWL_Edit::HitTest(const CFX_PointF& point) {
124   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_OuterScrollbar) {
125     if (IsShowVertScrollBar()) {
126       if (m_pVertScrollBar->GetWidgetRect().Contains(point))
127         return FWL_WidgetHit::VScrollBar;
128     }
129   }
130   if (m_ClientRect.Contains(point))
131     return FWL_WidgetHit::Edit;
132   return FWL_WidgetHit::Unknown;
133 }
134 
DrawWidget(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & matrix)135 void CFWL_Edit::DrawWidget(CFGAS_GEGraphics* pGraphics,
136                            const CFX_Matrix& matrix) {
137   if (!pGraphics)
138     return;
139 
140   if (m_ClientRect.IsEmpty())
141     return;
142 
143   DrawContent(pGraphics, matrix);
144   if (HasBorder())
145     DrawBorder(pGraphics, CFWL_ThemePart::Part::kBorder, matrix);
146 }
147 
SetText(const WideString & wsText)148 void CFWL_Edit::SetText(const WideString& wsText) {
149   m_pEditEngine->Clear();
150   m_pEditEngine->Insert(0, wsText,
151                         CFDE_TextEditEngine::RecordOperation::kInsertRecord);
152 }
153 
SetTextSkipNotify(const WideString & wsText)154 void CFWL_Edit::SetTextSkipNotify(const WideString& wsText) {
155   m_pEditEngine->Clear();
156   m_pEditEngine->Insert(0, wsText,
157                         CFDE_TextEditEngine::RecordOperation::kSkipNotify);
158 }
159 
GetTextLength() const160 size_t CFWL_Edit::GetTextLength() const {
161   return m_pEditEngine->GetLength();
162 }
163 
GetText() const164 WideString CFWL_Edit::GetText() const {
165   return m_pEditEngine->GetText();
166 }
167 
ClearText()168 void CFWL_Edit::ClearText() {
169   m_pEditEngine->Clear();
170 }
171 
SelectAll()172 void CFWL_Edit::SelectAll() {
173   m_pEditEngine->SelectAll();
174 }
175 
HasSelection() const176 bool CFWL_Edit::HasSelection() const {
177   return m_pEditEngine->HasSelection();
178 }
179 
GetSelection() const180 std::pair<size_t, size_t> CFWL_Edit::GetSelection() const {
181   return m_pEditEngine->GetSelection();
182 }
183 
ClearSelection()184 void CFWL_Edit::ClearSelection() {
185   return m_pEditEngine->ClearSelection();
186 }
187 
GetLimit() const188 int32_t CFWL_Edit::GetLimit() const {
189   return m_nLimit;
190 }
191 
SetLimit(int32_t nLimit)192 void CFWL_Edit::SetLimit(int32_t nLimit) {
193   m_nLimit = nLimit;
194 
195   if (m_nLimit > 0) {
196     m_pEditEngine->SetHasCharacterLimit(true);
197     m_pEditEngine->SetCharacterLimit(nLimit);
198   } else {
199     m_pEditEngine->SetHasCharacterLimit(false);
200   }
201 }
202 
SetAliasChar(wchar_t wAlias)203 void CFWL_Edit::SetAliasChar(wchar_t wAlias) {
204   m_pEditEngine->SetAliasChar(wAlias);
205 }
206 
Copy()207 absl::optional<WideString> CFWL_Edit::Copy() {
208   if (!m_pEditEngine->HasSelection())
209     return absl::nullopt;
210 
211   return m_pEditEngine->GetSelectedText();
212 }
213 
Cut()214 absl::optional<WideString> CFWL_Edit::Cut() {
215   if (!m_pEditEngine->HasSelection())
216     return absl::nullopt;
217 
218   WideString cut_text = m_pEditEngine->DeleteSelectedText();
219   UpdateCaret();
220   return cut_text;
221 }
222 
Paste(const WideString & wsPaste)223 bool CFWL_Edit::Paste(const WideString& wsPaste) {
224   if (m_pEditEngine->HasSelection())
225     m_pEditEngine->ReplaceSelectedText(wsPaste);
226   else
227     m_pEditEngine->Insert(m_CursorPosition, wsPaste);
228 
229   return true;
230 }
231 
Undo()232 bool CFWL_Edit::Undo() {
233   return CanUndo() && m_pEditEngine->Undo();
234 }
235 
Redo()236 bool CFWL_Edit::Redo() {
237   return CanRedo() && m_pEditEngine->Redo();
238 }
239 
CanUndo()240 bool CFWL_Edit::CanUndo() {
241   return m_pEditEngine->CanUndo();
242 }
243 
CanRedo()244 bool CFWL_Edit::CanRedo() {
245   return m_pEditEngine->CanRedo();
246 }
247 
NotifyTextFull()248 void CFWL_Edit::NotifyTextFull() {
249   CFWL_Event evt(CFWL_Event::Type::TextFull, this);
250   DispatchEvent(&evt);
251 }
252 
OnCaretChanged()253 void CFWL_Edit::OnCaretChanged() {
254   if (m_EngineRect.IsEmpty())
255     return;
256   if ((m_Properties.m_dwStates & FWL_STATE_WGT_Focused) == 0)
257     return;
258 
259   bool bRepaintContent = UpdateOffset();
260   UpdateCaret();
261   CFX_RectF rtInvalid;
262   bool bRepaintScroll = false;
263   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_MultiLine) {
264     CFWL_ScrollBar* pScroll = UpdateScroll();
265     if (pScroll) {
266       rtInvalid = pScroll->GetWidgetRect();
267       bRepaintScroll = true;
268     }
269   }
270   if (bRepaintContent || bRepaintScroll) {
271     if (bRepaintContent)
272       rtInvalid.Union(m_EngineRect);
273     RepaintRect(rtInvalid);
274   }
275 }
276 
OnTextWillChange(CFDE_TextEditEngine::TextChange * change)277 void CFWL_Edit::OnTextWillChange(CFDE_TextEditEngine::TextChange* change) {
278   CFWL_EventTextWillChange event(this, change->text, change->previous_text,
279                                  change->selection_start,
280                                  change->selection_end);
281   DispatchEvent(&event);
282 
283   change->text = event.GetChangeText();
284   change->selection_start = event.GetSelectionStart();
285   change->selection_end = event.GetSelectionEnd();
286   change->cancelled = event.GetCancelled();
287 }
288 
OnTextChanged()289 void CFWL_Edit::OnTextChanged() {
290   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_VAlignMask)
291     UpdateVAlignment();
292 
293   LayoutScrollBar();
294   RepaintRect(GetClientRect());
295 }
296 
OnSelChanged()297 void CFWL_Edit::OnSelChanged() {
298   RepaintRect(GetClientRect());
299 }
300 
OnValidate(const WideString & wsText)301 bool CFWL_Edit::OnValidate(const WideString& wsText) {
302   CFWL_EventValidate event(this, wsText);
303   DispatchEvent(&event);
304   return event.GetValidate();
305 }
306 
SetScrollOffset(float fScrollOffset)307 void CFWL_Edit::SetScrollOffset(float fScrollOffset) {
308   m_fScrollOffsetY = fScrollOffset;
309 }
310 
DrawContent(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & mtMatrix)311 void CFWL_Edit::DrawContent(CFGAS_GEGraphics* pGraphics,
312                             const CFX_Matrix& mtMatrix) {
313   DrawContentNonComb(pGraphics, mtMatrix);
314   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_CombText) {
315     CFGAS_GEGraphics::StateRestorer restorer(pGraphics);
316     CFGAS_GEPath path;
317     const int32_t iLimit = m_nLimit > 0 ? m_nLimit : 1;
318     const float fStep = m_EngineRect.width / iLimit;
319     float fLeft = m_EngineRect.left + 1;
320     for (int32_t i = 1; i < iLimit; i++) {
321       fLeft += fStep;
322       path.AddLine(CFX_PointF(fLeft, m_ClientRect.top),
323                    CFX_PointF(fLeft, m_ClientRect.bottom()));
324     }
325     CFWL_ThemeBackground param(CFWL_ThemePart::Part::kCombTextLine, this,
326                                pGraphics);
327     param.m_matrix = mtMatrix;
328     param.SetPath(&path);
329     GetThemeProvider()->DrawBackground(param);
330   }
331 }
332 
DrawContentNonComb(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & mtMatrix)333 void CFWL_Edit::DrawContentNonComb(CFGAS_GEGraphics* pGraphics,
334                                    const CFX_Matrix& mtMatrix) {
335   CFGAS_GEGraphics::StateRestorer restorer(pGraphics);
336   CFX_RectF rtClip = m_EngineRect;
337   float fOffSetX = m_EngineRect.left - m_fScrollOffsetX;
338   float fOffSetY = m_EngineRect.top - m_fScrollOffsetY + m_fVAlignOffset;
339   CFX_Matrix mt(1, 0, 0, 1, fOffSetX, fOffSetY);
340   rtClip = mtMatrix.TransformRect(rtClip);
341   mt.Concat(mtMatrix);
342 
343   bool bShowSel = !!(m_Properties.m_dwStates & FWL_STATE_WGT_Focused);
344   if (bShowSel && m_pEditEngine->HasSelection()) {
345     size_t sel_start;
346     size_t count;
347     std::tie(sel_start, count) = m_pEditEngine->GetSelection();
348     std::vector<CFX_RectF> rects = m_pEditEngine->GetCharacterRectsInRange(
349         pdfium::base::checked_cast<int32_t>(sel_start),
350         pdfium::base::checked_cast<int32_t>(count));
351 
352     CFGAS_GEPath path;
353     for (auto& rect : rects) {
354       rect.left += fOffSetX;
355       rect.top += fOffSetY;
356       path.AddRectangle(rect.left, rect.top, rect.width, rect.height);
357     }
358     pGraphics->SetClipRect(rtClip);
359 
360     CFWL_ThemeBackground param(CFWL_ThemePart::Part::kBackground, this,
361                                pGraphics);
362     param.m_matrix = mtMatrix;
363     param.SetPath(&path);
364     GetThemeProvider()->DrawBackground(param);
365   }
366 
367   CFX_RenderDevice* pRenderDev = pGraphics->GetRenderDevice();
368   RenderText(pRenderDev, rtClip, mt);
369 }
370 
RenderText(CFX_RenderDevice * pRenderDev,const CFX_RectF & clipRect,const CFX_Matrix & mt)371 void CFWL_Edit::RenderText(CFX_RenderDevice* pRenderDev,
372                            const CFX_RectF& clipRect,
373                            const CFX_Matrix& mt) {
374   DCHECK(pRenderDev);
375 
376   RetainPtr<CFGAS_GEFont> font = m_pEditEngine->GetFont();
377   if (!font)
378     return;
379 
380   pRenderDev->SetClip_Rect(clipRect.GetOuterRect());
381 
382   CFX_RectF rtDocClip = clipRect;
383   if (rtDocClip.IsEmpty()) {
384     rtDocClip.left = 0;
385     rtDocClip.top = 0;
386     rtDocClip.width = static_cast<float>(pRenderDev->GetWidth());
387     rtDocClip.height = static_cast<float>(pRenderDev->GetHeight());
388   }
389   rtDocClip = mt.GetInverse().TransformRect(rtDocClip);
390 
391   for (const FDE_TEXTEDITPIECE& info : m_pEditEngine->GetTextPieces()) {
392     // If this character is outside the clip, skip it.
393     if (!rtDocClip.IntersectWith(info.rtPiece))
394       continue;
395 
396     std::vector<TextCharPos> char_pos = m_pEditEngine->GetDisplayPos(info);
397     if (char_pos.empty())
398       continue;
399 
400     CFDE_TextOut::DrawString(pRenderDev, m_pEditEngine->GetFontColor(), font,
401                              char_pos, m_pEditEngine->GetFontSize(), mt);
402   }
403 }
404 
UpdateEditEngine()405 void CFWL_Edit::UpdateEditEngine() {
406   UpdateEditParams();
407   UpdateEditLayout();
408 }
409 
UpdateEditParams()410 void CFWL_Edit::UpdateEditParams() {
411   m_pEditEngine->SetAvailableWidth(m_EngineRect.width);
412   m_pEditEngine->SetCombText(
413       !!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_CombText));
414 
415   m_pEditEngine->EnableValidation(
416       !!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_Validate));
417   m_pEditEngine->EnablePasswordMode(
418       !!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_Password));
419 
420   uint32_t alignment = 0;
421   switch (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_HAlignMask) {
422     case FWL_STYLEEXT_EDT_HNear: {
423       alignment |= CFX_TxtLineAlignment_Left;
424       break;
425     }
426     case FWL_STYLEEXT_EDT_HCenter: {
427       alignment |= CFX_TxtLineAlignment_Center;
428       break;
429     }
430     case FWL_STYLEEXT_EDT_HFar: {
431       alignment |= CFX_TxtLineAlignment_Right;
432       break;
433     }
434     default:
435       break;
436   }
437   switch (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_HAlignModeMask) {
438     case FWL_STYLEEXT_EDT_Justified: {
439       alignment |= CFX_TxtLineAlignment_Justified;
440       break;
441     }
442     default:
443       break;
444   }
445   m_pEditEngine->SetAlignment(alignment);
446 
447   bool auto_hscroll =
448       !!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_AutoHScroll);
449   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_MultiLine) {
450     m_pEditEngine->EnableMultiLine(true);
451     m_pEditEngine->EnableLineWrap(!auto_hscroll);
452     m_pEditEngine->LimitVerticalScroll(
453         (m_Properties.m_dwStyles & FWL_STYLE_WGT_VScroll) == 0 &&
454         (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_AutoVScroll) == 0);
455   } else {
456     m_pEditEngine->EnableMultiLine(false);
457     m_pEditEngine->EnableLineWrap(false);
458     m_pEditEngine->LimitVerticalScroll(false);
459   }
460   m_pEditEngine->LimitHorizontalScroll(!auto_hscroll);
461 
462   IFWL_ThemeProvider* theme = GetThemeProvider();
463   CFWL_ThemePart part(CFWL_ThemePart::Part::kNone, this);
464   m_fFontSize = theme->GetFontSize(part);
465 
466   RetainPtr<CFGAS_GEFont> pFont = theme->GetFont(part);
467   if (!pFont)
468     return;
469 
470   m_pEditEngine->SetFont(pFont);
471   m_pEditEngine->SetFontColor(theme->GetTextColor(part));
472   m_pEditEngine->SetFontSize(m_fFontSize);
473   m_pEditEngine->SetLineSpace(theme->GetLineHeight(part));
474   m_pEditEngine->SetTabWidth(m_fFontSize);
475   m_pEditEngine->SetVisibleLineCount(m_EngineRect.height /
476                                      theme->GetLineHeight(part));
477 }
478 
UpdateEditLayout()479 void CFWL_Edit::UpdateEditLayout() {
480   m_pEditEngine->Layout();
481 }
482 
UpdateOffset()483 bool CFWL_Edit::UpdateOffset() {
484   CFX_RectF rtCaret = m_CaretRect;
485 
486   float fOffSetX = m_EngineRect.left - m_fScrollOffsetX;
487   float fOffSetY = m_EngineRect.top - m_fScrollOffsetY + m_fVAlignOffset;
488   rtCaret.Offset(fOffSetX, fOffSetY);
489 
490   const CFX_RectF& edit_bounds = m_EngineRect;
491   if (edit_bounds.Contains(rtCaret)) {
492     CFX_RectF contents_bounds = m_pEditEngine->GetContentsBoundingBox();
493     contents_bounds.Offset(fOffSetX, fOffSetY);
494     if (contents_bounds.right() < edit_bounds.right() && m_fScrollOffsetX > 0) {
495       m_fScrollOffsetX += contents_bounds.right() - edit_bounds.right();
496       m_fScrollOffsetX = std::max(m_fScrollOffsetX, 0.0f);
497     }
498     if (contents_bounds.bottom() < edit_bounds.bottom() &&
499         m_fScrollOffsetY > 0) {
500       m_fScrollOffsetY += contents_bounds.bottom() - edit_bounds.bottom();
501       m_fScrollOffsetY = std::max(m_fScrollOffsetY, 0.0f);
502     }
503     return false;
504   }
505 
506   float offsetX = 0.0;
507   float offsetY = 0.0;
508   if (rtCaret.left < edit_bounds.left)
509     offsetX = rtCaret.left - edit_bounds.left;
510   if (rtCaret.right() > edit_bounds.right())
511     offsetX = rtCaret.right() - edit_bounds.right();
512   if (rtCaret.top < edit_bounds.top)
513     offsetY = rtCaret.top - edit_bounds.top;
514   if (rtCaret.bottom() > edit_bounds.bottom())
515     offsetY = rtCaret.bottom() - edit_bounds.bottom();
516 
517   m_fScrollOffsetX += offsetX;
518   m_fScrollOffsetY += offsetY;
519   if (m_fFontSize > m_EngineRect.height)
520     m_fScrollOffsetY = 0;
521 
522   return true;
523 }
524 
UpdateOffset(CFWL_ScrollBar * pScrollBar,float fPosChanged)525 bool CFWL_Edit::UpdateOffset(CFWL_ScrollBar* pScrollBar, float fPosChanged) {
526   m_fScrollOffsetY += fPosChanged;
527   return true;
528 }
529 
UpdateVAlignment()530 void CFWL_Edit::UpdateVAlignment() {
531   IFWL_ThemeProvider* theme = GetThemeProvider();
532   CFWL_ThemePart part(CFWL_ThemePart::Part::kNone, this);
533   const CFX_SizeF pSpace = theme->GetSpaceAboveBelow(part);
534   const float fSpaceAbove = pSpace.width >= 0.1f ? pSpace.width : 0.0f;
535   const float fSpaceBelow = pSpace.height >= 0.1f ? pSpace.height : 0.0f;
536   float fOffsetY = 0.0f;
537   CFX_RectF contents_bounds = m_pEditEngine->GetContentsBoundingBox();
538   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_VCenter) {
539     fOffsetY = (m_EngineRect.height - contents_bounds.height) / 2.0f;
540     if (fOffsetY < (fSpaceAbove + fSpaceBelow) / 2.0f &&
541         fSpaceAbove < fSpaceBelow) {
542       return;
543     }
544     fOffsetY += (fSpaceAbove - fSpaceBelow) / 2.0f;
545   } else if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_VFar) {
546     fOffsetY = (m_EngineRect.height - contents_bounds.height);
547     fOffsetY -= fSpaceBelow;
548   } else {
549     fOffsetY += fSpaceAbove;
550   }
551   m_fVAlignOffset = std::max(fOffsetY, 0.0f);
552 }
553 
UpdateCaret()554 void CFWL_Edit::UpdateCaret() {
555   CFX_RectF rtCaret = m_CaretRect;
556   rtCaret.Offset(m_EngineRect.left - m_fScrollOffsetX,
557                  m_EngineRect.top - m_fScrollOffsetY + m_fVAlignOffset);
558 
559   CFX_RectF rtClient = GetClientRect();
560   rtCaret.Intersect(rtClient);
561   if (rtCaret.left > rtClient.right()) {
562     float right = rtCaret.right();
563     rtCaret.left = rtClient.right() - 1;
564     rtCaret.width = right - rtCaret.left;
565   }
566 
567   if (m_Properties.m_dwStates & FWL_STATE_WGT_Focused && !rtCaret.IsEmpty())
568     ShowCaret(&rtCaret);
569   else
570     HideCaret(&rtCaret);
571 }
572 
UpdateScroll()573 CFWL_ScrollBar* CFWL_Edit::UpdateScroll() {
574   bool bShowVert = m_pVertScrollBar && m_pVertScrollBar->IsVisible();
575   if (!bShowVert)
576     return nullptr;
577 
578   CFX_RectF contents_bounds = m_pEditEngine->GetContentsBoundingBox();
579   CFX_RectF rtScroll = m_pVertScrollBar->GetWidgetRect();
580   if (rtScroll.height < contents_bounds.height) {
581     float fStep = m_pEditEngine->GetLineSpace();
582     float fRange =
583         std::max(contents_bounds.height - m_EngineRect.height, fStep);
584     m_pVertScrollBar->SetRange(0.0f, fRange);
585     float fPos = std::clamp(m_fScrollOffsetY, 0.0f, fRange);
586     m_pVertScrollBar->SetPos(fPos);
587     m_pVertScrollBar->SetTrackPos(fPos);
588     m_pVertScrollBar->SetPageSize(rtScroll.height);
589     m_pVertScrollBar->SetStepSize(fStep);
590     m_pVertScrollBar->RemoveStates(FWL_STATE_WGT_Disabled);
591     m_pVertScrollBar->Update();
592     return m_pVertScrollBar;
593   }
594   if ((m_pVertScrollBar->GetStates() & FWL_STATE_WGT_Disabled) == 0) {
595     m_pVertScrollBar->SetRange(0, -1);
596     m_pVertScrollBar->SetStates(FWL_STATE_WGT_Disabled);
597     m_pVertScrollBar->Update();
598     return m_pVertScrollBar;
599   }
600   return nullptr;
601 }
602 
IsShowVertScrollBar() const603 bool CFWL_Edit::IsShowVertScrollBar() const {
604   const bool bShow =
605       !(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_ShowScrollbarFocus) ||
606       (m_Properties.m_dwStates & FWL_STATE_WGT_Focused);
607   return bShow && (m_Properties.m_dwStyles & FWL_STYLE_WGT_VScroll) &&
608          (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_MultiLine) &&
609          IsContentHeightOverflow();
610 }
611 
IsContentHeightOverflow() const612 bool CFWL_Edit::IsContentHeightOverflow() const {
613   return m_pEditEngine->GetContentsBoundingBox().height >
614          m_EngineRect.height + 1.0f;
615 }
616 
Layout()617 void CFWL_Edit::Layout() {
618   m_ClientRect = GetClientRect();
619   m_EngineRect = m_ClientRect;
620 
621   IFWL_ThemeProvider* theme = GetThemeProvider();
622   float fWidth = theme->GetScrollBarWidth();
623   if (!GetOuter()) {
624     CFWL_ThemePart part(CFWL_ThemePart::Part::kNone, this);
625     CFX_RectF pUIMargin = theme->GetUIMargin(part);
626     m_EngineRect.Deflate(pUIMargin.left, pUIMargin.top, pUIMargin.width,
627                          pUIMargin.height);
628   } else if (GetOuter()->GetClassID() == FWL_Type::DateTimePicker) {
629     CFWL_ThemePart part(CFWL_ThemePart::Part::kNone, GetOuter());
630     CFX_RectF pUIMargin = theme->GetUIMargin(part);
631     m_EngineRect.Deflate(pUIMargin.left, pUIMargin.top, pUIMargin.width,
632                          pUIMargin.height);
633   }
634 
635   bool bShowVertScrollbar = IsShowVertScrollBar();
636   if (bShowVertScrollbar) {
637     InitVerticalScrollBar();
638 
639     CFX_RectF rtVertScr;
640     if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_OuterScrollbar) {
641       rtVertScr = CFX_RectF(m_ClientRect.right() + kEditMargin,
642                             m_ClientRect.top, fWidth, m_ClientRect.height);
643     } else {
644       rtVertScr = CFX_RectF(m_ClientRect.right() - fWidth, m_ClientRect.top,
645                             fWidth, m_ClientRect.height);
646       m_EngineRect.width -= fWidth;
647     }
648 
649     m_pVertScrollBar->SetWidgetRect(rtVertScr);
650     m_pVertScrollBar->RemoveStates(FWL_STATE_WGT_Invisible);
651     m_pVertScrollBar->Update();
652   } else if (m_pVertScrollBar) {
653     m_pVertScrollBar->SetStates(FWL_STATE_WGT_Invisible);
654   }
655 }
656 
LayoutScrollBar()657 void CFWL_Edit::LayoutScrollBar() {
658   if (!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_ShowScrollbarFocus))
659     return;
660 
661   bool bShowVertScrollbar = IsShowVertScrollBar();
662   IFWL_ThemeProvider* theme = GetThemeProvider();
663   float fWidth = theme->GetScrollBarWidth();
664   if (bShowVertScrollbar) {
665     if (!m_pVertScrollBar) {
666       InitVerticalScrollBar();
667       CFX_RectF rtVertScr;
668       if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_OuterScrollbar) {
669         rtVertScr = CFX_RectF(m_ClientRect.right() + kEditMargin,
670                               m_ClientRect.top, fWidth, m_ClientRect.height);
671       } else {
672         rtVertScr = CFX_RectF(m_ClientRect.right() - fWidth, m_ClientRect.top,
673                               fWidth, m_ClientRect.height);
674       }
675       m_pVertScrollBar->SetWidgetRect(rtVertScr);
676       m_pVertScrollBar->Update();
677     }
678     m_pVertScrollBar->RemoveStates(FWL_STATE_WGT_Invisible);
679   } else if (m_pVertScrollBar) {
680     m_pVertScrollBar->SetStates(FWL_STATE_WGT_Invisible);
681   }
682   if (bShowVertScrollbar)
683     UpdateScroll();
684 }
685 
DeviceToEngine(const CFX_PointF & pt)686 CFX_PointF CFWL_Edit::DeviceToEngine(const CFX_PointF& pt) {
687   return pt + CFX_PointF(m_fScrollOffsetX - m_EngineRect.left,
688                          m_fScrollOffsetY - m_EngineRect.top - m_fVAlignOffset);
689 }
690 
InitVerticalScrollBar()691 void CFWL_Edit::InitVerticalScrollBar() {
692   if (m_pVertScrollBar)
693     return;
694 
695   m_pVertScrollBar = cppgc::MakeGarbageCollected<CFWL_ScrollBar>(
696       GetFWLApp()->GetHeap()->GetAllocationHandle(), GetFWLApp(),
697       Properties{0, FWL_STYLEEXT_SCB_Vert,
698                  FWL_STATE_WGT_Disabled | FWL_STATE_WGT_Invisible},
699       this);
700 }
701 
ShowCaret(CFX_RectF * pRect)702 void CFWL_Edit::ShowCaret(CFX_RectF* pRect) {
703   if (m_pCaret) {
704     m_pCaret->ShowCaret();
705     if (!pRect->IsEmpty())
706       m_pCaret->SetWidgetRect(*pRect);
707     RepaintRect(m_EngineRect);
708     return;
709   }
710 
711   CFWL_Widget* pOuter = this;
712   pRect->Offset(m_WidgetRect.left, m_WidgetRect.top);
713   while (pOuter->GetOuter()) {
714     pOuter = pOuter->GetOuter();
715     CFX_RectF rtOuter = pOuter->GetWidgetRect();
716     pRect->Offset(rtOuter.left, rtOuter.top);
717   }
718 
719   CFWL_Widget::AdapterIface* pXFAWidget = pOuter->GetAdapterIface();
720   if (!pXFAWidget)
721     return;
722 
723   CFX_RectF rt = pXFAWidget->GetRotateMatrix().TransformRect(*pRect);
724   pXFAWidget->DisplayCaret(true, &rt);
725 }
726 
HideCaret(CFX_RectF * pRect)727 void CFWL_Edit::HideCaret(CFX_RectF* pRect) {
728   if (m_pCaret) {
729     m_pCaret->HideCaret();
730     RepaintRect(m_EngineRect);
731     return;
732   }
733 
734   CFWL_Widget* pOuter = this;
735   while (pOuter->GetOuter())
736     pOuter = pOuter->GetOuter();
737 
738   CFWL_Widget::AdapterIface* pXFAWidget = pOuter->GetAdapterIface();
739   if (!pXFAWidget)
740     return;
741 
742   pXFAWidget->DisplayCaret(false, pRect);
743 }
744 
InitCaret()745 void CFWL_Edit::InitCaret() {
746   if (m_pCaret)
747     return;
748 
749   m_pCaret = cppgc::MakeGarbageCollected<CFWL_Caret>(
750       GetFWLApp()->GetHeap()->GetAllocationHandle(), GetFWLApp(), Properties(),
751       this);
752   m_pCaret->SetStates(m_Properties.m_dwStates);
753   UpdateCursorRect();
754 }
755 
UpdateCursorRect()756 void CFWL_Edit::UpdateCursorRect() {
757   int32_t bidi_level;
758   if (m_pEditEngine->CanGenerateCharacterInfo()) {
759     std::tie(bidi_level, m_CaretRect) = m_pEditEngine->GetCharacterInfo(
760         pdfium::base::checked_cast<int32_t>(m_CursorPosition));
761   } else {
762     bidi_level = 0;
763     m_CaretRect = CFX_RectF();
764   }
765 
766   // TODO(dsinclair): This should handle bidi level  ...
767 
768   m_CaretRect.width = 1.0f;
769 
770   // TODO(hnakashima): Handle correctly edits with empty text instead of using
771   // these defaults.
772   if (m_CaretRect.height == 0)
773     m_CaretRect.height = 8.0f;
774 }
775 
SetCursorPosition(size_t position)776 void CFWL_Edit::SetCursorPosition(size_t position) {
777   if (m_CursorPosition == position)
778     return;
779 
780   m_CursorPosition = std::min(position, m_pEditEngine->GetLength());
781   UpdateCursorRect();
782   OnCaretChanged();
783 }
784 
OnProcessMessage(CFWL_Message * pMessage)785 void CFWL_Edit::OnProcessMessage(CFWL_Message* pMessage) {
786   switch (pMessage->GetType()) {
787     case CFWL_Message::Type::kSetFocus:
788       OnFocusGained();
789       break;
790     case CFWL_Message::Type::kKillFocus:
791       OnFocusLost();
792       break;
793     case CFWL_Message::Type::kMouse: {
794       CFWL_MessageMouse* pMsg = static_cast<CFWL_MessageMouse*>(pMessage);
795       switch (pMsg->m_dwCmd) {
796         case CFWL_MessageMouse::MouseCommand::kLeftButtonDown:
797           OnLButtonDown(pMsg);
798           break;
799         case CFWL_MessageMouse::MouseCommand::kLeftButtonUp:
800           OnLButtonUp(pMsg);
801           break;
802         case CFWL_MessageMouse::MouseCommand::kLeftButtonDblClk:
803           OnButtonDoubleClick(pMsg);
804           break;
805         case CFWL_MessageMouse::MouseCommand::kMove:
806           OnMouseMove(pMsg);
807           break;
808         case CFWL_MessageMouse::MouseCommand::kRightButtonDown:
809           DoRButtonDown(pMsg);
810           break;
811         default:
812           break;
813       }
814       break;
815     }
816     case CFWL_Message::Type::kKey: {
817       CFWL_MessageKey* pKey = static_cast<CFWL_MessageKey*>(pMessage);
818       if (pKey->m_dwCmd == CFWL_MessageKey::KeyCommand::kKeyDown)
819         OnKeyDown(pKey);
820       else if (pKey->m_dwCmd == CFWL_MessageKey::KeyCommand::kChar)
821         OnChar(pKey);
822       break;
823     }
824     default:
825       break;
826   }
827   // Dst target could be |this|, continue only if not destroyed by above.
828   if (pMessage->GetDstTarget())
829     CFWL_Widget::OnProcessMessage(pMessage);
830 }
831 
OnProcessEvent(CFWL_Event * pEvent)832 void CFWL_Edit::OnProcessEvent(CFWL_Event* pEvent) {
833   if (!pEvent || pEvent->GetType() != CFWL_Event::Type::Scroll)
834     return;
835 
836   CFWL_Widget* pSrcTarget = pEvent->GetSrcTarget();
837   if ((pSrcTarget == m_pVertScrollBar && m_pVertScrollBar)) {
838     CFWL_EventScroll* pScrollEvent = static_cast<CFWL_EventScroll*>(pEvent);
839     OnScroll(static_cast<CFWL_ScrollBar*>(pSrcTarget),
840              pScrollEvent->GetScrollCode(), pScrollEvent->GetPos());
841   }
842 }
843 
OnDrawWidget(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & matrix)844 void CFWL_Edit::OnDrawWidget(CFGAS_GEGraphics* pGraphics,
845                              const CFX_Matrix& matrix) {
846   DrawWidget(pGraphics, matrix);
847 }
848 
DoRButtonDown(CFWL_MessageMouse * pMsg)849 void CFWL_Edit::DoRButtonDown(CFWL_MessageMouse* pMsg) {
850   SetCursorPosition(
851       m_pEditEngine->GetIndexForPoint(DeviceToEngine(pMsg->m_pos)));
852 }
853 
OnFocusGained()854 void CFWL_Edit::OnFocusGained() {
855   m_Properties.m_dwStates |= FWL_STATE_WGT_Focused;
856   UpdateVAlignment();
857   UpdateOffset();
858   UpdateCaret();
859   LayoutScrollBar();
860 }
861 
OnFocusLost()862 void CFWL_Edit::OnFocusLost() {
863   bool bRepaint = false;
864   if (m_Properties.m_dwStates & FWL_STATE_WGT_Focused) {
865     m_Properties.m_dwStates &= ~FWL_STATE_WGT_Focused;
866     HideCaret(nullptr);
867     if (HasSelection()) {
868       ClearSelection();
869       bRepaint = true;
870     }
871     UpdateOffset();
872   }
873   LayoutScrollBar();
874   if (!bRepaint)
875     return;
876 
877   RepaintRect(CFX_RectF(0, 0, m_WidgetRect.width, m_WidgetRect.height));
878 }
879 
OnLButtonDown(CFWL_MessageMouse * pMsg)880 void CFWL_Edit::OnLButtonDown(CFWL_MessageMouse* pMsg) {
881   if (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)
882     return;
883 
884   m_bLButtonDown = true;
885   SetGrab(true);
886 
887   bool bRepaint = false;
888   if (m_pEditEngine->HasSelection()) {
889     m_pEditEngine->ClearSelection();
890     bRepaint = true;
891   }
892 
893   size_t index_at_click =
894       m_pEditEngine->GetIndexForPoint(DeviceToEngine(pMsg->m_pos));
895 
896   if (index_at_click != m_CursorPosition &&
897       !!(pMsg->m_dwFlags & XFA_FWL_KeyFlag::kShift)) {
898     size_t start = std::min(m_CursorPosition, index_at_click);
899     size_t end = std::max(m_CursorPosition, index_at_click);
900 
901     m_pEditEngine->SetSelection(start, end - start);
902     bRepaint = true;
903   } else {
904     SetCursorPosition(index_at_click);
905   }
906 
907   if (bRepaint)
908     RepaintRect(m_EngineRect);
909 }
910 
OnLButtonUp(CFWL_MessageMouse * pMsg)911 void CFWL_Edit::OnLButtonUp(CFWL_MessageMouse* pMsg) {
912   m_bLButtonDown = false;
913   SetGrab(false);
914 }
915 
OnButtonDoubleClick(CFWL_MessageMouse * pMsg)916 void CFWL_Edit::OnButtonDoubleClick(CFWL_MessageMouse* pMsg) {
917   size_t click_idx =
918       m_pEditEngine->GetIndexForPoint(DeviceToEngine(pMsg->m_pos));
919   size_t start_idx;
920   size_t count;
921   std::tie(start_idx, count) = m_pEditEngine->BoundsForWordAt(click_idx);
922 
923   m_pEditEngine->SetSelection(start_idx, count);
924   m_CursorPosition = start_idx + count;
925   RepaintRect(m_EngineRect);
926 }
927 
OnMouseMove(CFWL_MessageMouse * pMsg)928 void CFWL_Edit::OnMouseMove(CFWL_MessageMouse* pMsg) {
929   bool shift = !!(pMsg->m_dwFlags & XFA_FWL_KeyFlag::kShift);
930   if (!m_bLButtonDown || !shift)
931     return;
932 
933   size_t old_cursor_pos = m_CursorPosition;
934   SetCursorPosition(
935       m_pEditEngine->GetIndexForPoint(DeviceToEngine(pMsg->m_pos)));
936   if (old_cursor_pos == m_CursorPosition)
937     return;
938 
939   size_t length = m_pEditEngine->GetLength();
940   if (m_CursorPosition > length)
941     SetCursorPosition(length);
942 
943   size_t sel_start = 0;
944   size_t count = 0;
945   if (m_pEditEngine->HasSelection())
946     std::tie(sel_start, count) = m_pEditEngine->GetSelection();
947   else
948     sel_start = old_cursor_pos;
949 
950   size_t start_pos = std::min(sel_start, m_CursorPosition);
951   size_t end_pos = std::max(sel_start, m_CursorPosition);
952   m_pEditEngine->SetSelection(start_pos, end_pos - start_pos);
953 }
954 
OnKeyDown(CFWL_MessageKey * pMsg)955 void CFWL_Edit::OnKeyDown(CFWL_MessageKey* pMsg) {
956   bool bShift = !!(pMsg->m_dwFlags & XFA_FWL_KeyFlag::kShift);
957   bool bCtrl = !!(pMsg->m_dwFlags & XFA_FWL_KeyFlag::kCtrl);
958 
959   size_t sel_start = m_CursorPosition;
960   if (m_pEditEngine->HasSelection()) {
961     size_t start_idx;
962     size_t count;
963     std::tie(start_idx, count) = m_pEditEngine->GetSelection();
964     sel_start = start_idx;
965   }
966 
967   switch (pMsg->m_dwKeyCodeOrChar) {
968     case XFA_FWL_VKEY_Left:
969       SetCursorPosition(m_pEditEngine->GetIndexLeft(m_CursorPosition));
970       break;
971     case XFA_FWL_VKEY_Right:
972       SetCursorPosition(m_pEditEngine->GetIndexRight(m_CursorPosition));
973       break;
974     case XFA_FWL_VKEY_Up:
975       SetCursorPosition(m_pEditEngine->GetIndexUp(m_CursorPosition));
976       break;
977     case XFA_FWL_VKEY_Down:
978       SetCursorPosition(m_pEditEngine->GetIndexDown(m_CursorPosition));
979       break;
980     case XFA_FWL_VKEY_Home:
981       SetCursorPosition(
982           bCtrl ? 0 : m_pEditEngine->GetIndexAtStartOfLine(m_CursorPosition));
983       break;
984     case XFA_FWL_VKEY_End:
985       SetCursorPosition(
986           bCtrl ? m_pEditEngine->GetLength()
987                 : m_pEditEngine->GetIndexAtEndOfLine(m_CursorPosition));
988       break;
989     case XFA_FWL_VKEY_Delete: {
990       if ((m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_ReadOnly) ||
991           (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)) {
992         break;
993       }
994 
995       m_pEditEngine->Delete(m_CursorPosition, 1);
996       UpdateCaret();
997       break;
998     }
999     case XFA_FWL_VKEY_Insert:
1000     case XFA_FWL_VKEY_F2:
1001     case XFA_FWL_VKEY_Tab:
1002     default:
1003       break;
1004   }
1005 
1006   // Update the selection.
1007   if (bShift && sel_start != m_CursorPosition) {
1008     m_pEditEngine->SetSelection(std::min(sel_start, m_CursorPosition),
1009                                 std::max(sel_start, m_CursorPosition));
1010     RepaintRect(m_EngineRect);
1011   }
1012 }
1013 
OnChar(CFWL_MessageKey * pMsg)1014 void CFWL_Edit::OnChar(CFWL_MessageKey* pMsg) {
1015   if ((m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_ReadOnly) ||
1016       (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)) {
1017     return;
1018   }
1019 
1020   wchar_t c = static_cast<wchar_t>(pMsg->m_dwKeyCodeOrChar);
1021   switch (c) {
1022     case L'\b':
1023       if (m_CursorPosition > 0) {
1024         SetCursorPosition(m_CursorPosition - 1);
1025         m_pEditEngine->Delete(m_CursorPosition, 1);
1026         UpdateCaret();
1027       }
1028       break;
1029     case L'\n':
1030     case 27:   // Esc
1031     case 127:  // Delete
1032       break;
1033     case L'\t':
1034       m_pEditEngine->Insert(m_CursorPosition, L"\t");
1035       SetCursorPosition(m_CursorPosition + 1);
1036       break;
1037     case L'\r':
1038       if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_WantReturn) {
1039         m_pEditEngine->Insert(m_CursorPosition, L"\n");
1040         SetCursorPosition(m_CursorPosition + 1);
1041       }
1042       break;
1043     default: {
1044       if (pMsg->m_dwFlags & kEditingModifier)
1045         break;
1046 
1047       m_pEditEngine->Insert(m_CursorPosition, WideString(c));
1048       SetCursorPosition(m_CursorPosition + 1);
1049       break;
1050     }
1051   }
1052 }
1053 
OnScroll(CFWL_ScrollBar * pScrollBar,CFWL_EventScroll::Code dwCode,float fPos)1054 bool CFWL_Edit::OnScroll(CFWL_ScrollBar* pScrollBar,
1055                          CFWL_EventScroll::Code dwCode,
1056                          float fPos) {
1057   float fMin;
1058   float fMax;
1059   pScrollBar->GetRange(&fMin, &fMax);
1060   float iCurPos = pScrollBar->GetPos();
1061   float fStep = pScrollBar->GetStepSize();
1062   switch (dwCode) {
1063     case CFWL_EventScroll::Code::Min: {
1064       fPos = fMin;
1065       break;
1066     }
1067     case CFWL_EventScroll::Code::Max: {
1068       fPos = fMax;
1069       break;
1070     }
1071     case CFWL_EventScroll::Code::StepBackward: {
1072       fPos -= fStep;
1073       if (fPos < fMin + fStep / 2) {
1074         fPos = fMin;
1075       }
1076       break;
1077     }
1078     case CFWL_EventScroll::Code::StepForward: {
1079       fPos += fStep;
1080       if (fPos > fMax - fStep / 2) {
1081         fPos = fMax;
1082       }
1083       break;
1084     }
1085     case CFWL_EventScroll::Code::PageBackward: {
1086       fPos -= pScrollBar->GetPageSize();
1087       if (fPos < fMin) {
1088         fPos = fMin;
1089       }
1090       break;
1091     }
1092     case CFWL_EventScroll::Code::PageForward: {
1093       fPos += pScrollBar->GetPageSize();
1094       if (fPos > fMax) {
1095         fPos = fMax;
1096       }
1097       break;
1098     }
1099     case CFWL_EventScroll::Code::Pos:
1100     case CFWL_EventScroll::Code::TrackPos:
1101     case CFWL_EventScroll::Code::None:
1102       break;
1103     case CFWL_EventScroll::Code::EndScroll:
1104       return false;
1105   }
1106   if (iCurPos == fPos)
1107     return true;
1108 
1109   pScrollBar->SetPos(fPos);
1110   pScrollBar->SetTrackPos(fPos);
1111   UpdateOffset(pScrollBar, fPos - iCurPos);
1112   UpdateCaret();
1113 
1114   CFX_RectF rect = GetWidgetRect();
1115   RepaintRect(CFX_RectF(0, 0, rect.width + 2, rect.height + 2));
1116   return true;
1117 }
1118