• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gansm / finalcut / #760

06 Feb 2026 01:45AM UTC coverage: 69.064% (-0.02%) from 69.083%
#760

push

travis-ci

gansm
Reduce pointer arithmetic and use safe iterators more often

206 of 432 new or added lines in 27 files covered. (47.69%)

25 existing lines in 6 files now uncovered.

37622 of 54474 relevant lines covered (69.06%)

243.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/final/widget/ftextview.cpp
1
/***********************************************************************
2
* ftextview.cpp - Widget FTextView (a multiline text viewer)           *
3
*                                                                      *
4
* This file is part of the FINAL CUT widget toolkit                    *
5
*                                                                      *
6
* Copyright 2014-2026 Markus Gans                                      *
7
*                                                                      *
8
* FINAL CUT is free software; you can redistribute it and/or modify    *
9
* it under the terms of the GNU Lesser General Public License as       *
10
* published by the Free Software Foundation; either version 3 of       *
11
* the License, or (at your option) any later version.                  *
12
*                                                                      *
13
* FINAL CUT is distributed in the hope that it will be useful, but     *
14
* WITHOUT ANY WARRANTY; without even the implied warranty of           *
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
16
* GNU Lesser General Public License for more details.                  *
17
*                                                                      *
18
* You should have received a copy of the GNU Lesser General Public     *
19
* License along with this program.  If not, see                        *
20
* <http://www.gnu.org/licenses/>.                                      *
21
***********************************************************************/
22

23
#include <memory>
24

25
#include "final/dialog/fdialog.h"
26
#include "final/fapplication.h"
27
#include "final/fc.h"
28
#include "final/fevent.h"
29
#include "final/fwidgetcolors.h"
30
#include "final/util/fstring.h"
31
#include "final/vterm/fvtermbuffer.h"
32
#include "final/widget/fscrollbar.h"
33
#include "final/widget/fstatusbar.h"
34
#include "final/widget/ftextview.h"
35

36
namespace finalcut
37
{
38

39
//----------------------------------------------------------------------
40
// class FTextView
41
//----------------------------------------------------------------------
42

43
// constructor and destructor
44
//----------------------------------------------------------------------
45
FTextView::FTextView(FWidget* parent)
×
46
  : FWidget{parent}
×
47
{
48
  init();
×
49
}
×
50

51
//----------------------------------------------------------------------
52
FTextView::~FTextView() noexcept = default;  // destructor
×
53

54

55
// public methods of FTextView
56
//----------------------------------------------------------------------
57
auto FTextView::getText() const -> FString
×
58
{
59
  if ( data.empty() )
×
60
    return {""};
×
61

62
  std::size_t len{0};
×
63

64
  for (auto&& line : data)
×
65
    len += line.text.getLength() + 1;  // String length + '\n'
×
66

67
  FString s{len};  // Reserves storage
×
68
  auto iter = s.begin();
×
69

70
  for (auto&& line : data)
×
71
  {
72
    if ( line.text.isEmpty() )
×
73
      continue;
×
74

75
    if ( iter != s.begin() )
×
76
    {
77
      *iter = '\n';  // Add newline character
×
78
      ++iter;
×
79
    }
80

81
    std::copy (line.text.begin(), line.text.end(), iter);
×
82
    iter += std::distance(line.text.begin(), line.text.end());
×
83
  }
84

85
  return s;
×
86
}
×
87

88
//----------------------------------------------------------------------
89
auto FTextView::getSelectedText() const -> FString
×
90
{
91
  if ( ! hasSelectedText() )
×
92
    return {};
×
93

94
  const auto start_row = std::min(selection_start.row, selection_end.row);
×
95
  const auto end_row = std::max(selection_start.row, selection_end.row);
×
96
  const bool wrong_order = hasWrongSelectionOrder();
×
97
  const auto start_col = wrong_order ? selection_end.column
×
98
                                     : selection_start.column;
99
  const auto end_col = wrong_order ? selection_start.column
×
100
                                   : selection_end.column;
101
  const auto first = &getLine(start_row);
×
102
  const auto last = &getLine(end_row);
×
NEW
103
  const auto end = std::next(last);
×
104
  auto iter = first;
×
105
  FString selected_text{};
×
106
  std::wstring line{};
×
107

108
  while ( iter != end )
×
109
  {
110
    if ( iter == first )
×
111
    {
112
      if ( start_col >= iter->text.getLength() )
×
113
      {
NEW
114
        iter = std::next(iter);
×
115
        continue;
×
116
      }
117

118
      line = iter->text.toWString().substr(start_col);
×
119
    }
120
    else
121
      line = iter->text.toWString();
×
122

123
    if ( iter == last )
×
124
      line.resize(end_col + 1);
×
125

126
    selected_text += FString(line) + L'\n';  // Add newline character
×
NEW
127
    iter = std::next(iter);
×
128
  }
129

130
  return selected_text;
×
131
}
×
132

133
//----------------------------------------------------------------------
134
void FTextView::setSize (const FSize& size, bool adjust)
×
135
{
136
  // Sets the text view size
137

138
  FWidget::setSize (size, adjust);
×
139
  changeOnResize();
×
140
}
×
141

142
//----------------------------------------------------------------------
143
void FTextView::setGeometry ( const FPoint& pos, const FSize& size
×
144
                            , bool adjust)
145
{
146
  // Sets the text view geometry
147
  FWidget::setGeometry(pos, size, adjust);
×
148
  changeOnResize();
×
149
}
×
150

151
//----------------------------------------------------------------------
152
void FTextView::resetColors()
×
153
{
154
  const auto& wc_text = getColorTheme()->text;
×
155
  FWidget::setForegroundColor (wc_text.fg);
×
156
  FWidget::setBackgroundColor (wc_text.bg);
×
157
  FWidget::resetColors();
×
158
}
×
159

160
//----------------------------------------------------------------------
161
void FTextView::setText (const FString& str)
×
162
{
163
  clear();
×
164
  insert(str, -1);
×
165
}
×
166

167
//----------------------------------------------------------------------
168
void FTextView::addHighlight (std::size_t line, const FTextHighlight& hgl)
×
169
{
170
  if ( line >= data.size() )
×
171
    return;
×
172

173
  data[line].highlight.emplace_back(hgl);
×
174
}
175

176
//----------------------------------------------------------------------
177
void FTextView::resetHighlight (std::size_t line)
×
178
{
179
  if ( line >= data.size() )
×
180
    return;
×
181

182
  data[line].highlight.clear();
×
183
}
184

185
//----------------------------------------------------------------------
186
void FTextView::scrollToX (int x)
×
187
{
188
  scrollTo (x, yoffset);
×
189
}
×
190

191
//----------------------------------------------------------------------
192
void FTextView::scrollToY (int y)
×
193
{
194
  scrollTo (xoffset, y);
×
195
}
×
196

197
//----------------------------------------------------------------------
198
void FTextView::scrollBy (int dx, int dy)
×
199
{
200
  scrollTo (xoffset + dx, yoffset + dy);
×
201
}
×
202

203
//----------------------------------------------------------------------
204
void FTextView::scrollTo (int x, int y)
×
205
{
206
  const auto xoffset_end = int(max_line_width - getTextWidth());
×
207
  const auto yoffset_end = int(getRows() - getTextHeight());
×
208
  const bool changeX( x != xoffset );
×
209
  const bool changeY( y != yoffset );
×
210

211
  if ( ! isShown() || ! (changeX || changeY) )
×
212
    return;
×
213

214
  xoffset = std::max(0, std::min(x, xoffset_end));
×
215
  yoffset = std::max(0, std::min(y, yoffset_end));
×
216

217
  if ( update_scrollbar && changeX && isHorizontallyScrollable() )
×
218
  {
219
    hbar->setValue(xoffset);
×
220
    hbar->drawBar();
×
221
  }
222

223
  if ( update_scrollbar && changeY && isVerticallyScrollable() )
×
224
  {
225
    vbar->setValue(yoffset);
×
226
    vbar->drawBar();
×
227
  }
228

229
  drawText();
×
230
}
231

232
//----------------------------------------------------------------------
233
void FTextView::scrollToBegin()
×
234
{
235
  scrollToY (0);
×
236
}
×
237

238
//----------------------------------------------------------------------
239
void FTextView::scrollToEnd()
×
240
{
241
  scrollToY (int(getRows() - getTextHeight()));
×
242
}
×
243

244
//----------------------------------------------------------------------
245
void FTextView::hide()
×
246
{
247
  FWidget::hide();
×
248
  hideArea (getSize());
×
249
}
×
250

251
//----------------------------------------------------------------------
252
void FTextView::clear()
×
253
{
254
  data.clear();
×
255
  data.shrink_to_fit();
×
256
  xoffset = 0;
×
257
  yoffset = 0;
×
258
  max_line_width = 0;
×
259

260
  vbar->setMinimum(0);
×
261
  vbar->setValue(0);
×
262
  vbar->hide();
×
263

264
  hbar->setMinimum(0);
×
265
  hbar->setValue(0);
×
266
  hbar->hide();
×
267

268
  // clear list from screen
269
  setColor();
×
270

271
  if ( useFDialogBorder() )
×
272
  {
273
    auto parent = getParentWidget();
×
274

275
    if ( parent )
×
276
      static_cast<FDialog*>(parent)->redraw();
×
277
  }
278
  else
279
    drawBorder();
×
280

281
  const std::size_t size = getWidth() - 2;
×
282

283
  if ( size == 0 )
×
284
    return;
×
285

286
  for (auto y{0}; y < int(getTextHeight()); y++)
×
287
  {
288
    print() << FPoint{2, 2 - nf_offset + y}
×
289
            << FString{size, L' '};
×
290
  }
291

292
  processChanged();
×
293
}
294

295
//----------------------------------------------------------------------
296
void FTextView::append (const FString& str)
×
297
{
298
  insert(str, -1);
×
299
}
×
300

301
//----------------------------------------------------------------------
302
void FTextView::insert (const FString& str, int pos)
×
303
{
304
  if ( pos < 0 || pos >= int(getRows()) )
×
305
    pos = int(getRows());
×
306

307
  for (auto&& line : splitTextLines(str))  // Line loop
×
308
  {
309
    processLine(std::move(line), pos);
×
310
    pos++;
×
311
  }
×
312

313
  updateVerticalScrollBar();
×
314
  processChanged();
×
315
}
×
316

317
//----------------------------------------------------------------------
318
void FTextView::replaceRange (const FString& str, int from, int to)
×
319
{
320
  try
321
  {
322
    deleteRange (from, to);
×
323
  }
324
  catch (const std::out_of_range&)
×
325
  {
326
    throw std::out_of_range("FTextView::replaceRange index out of range");  // Invalid range
×
327
  }
×
328

329
  insert(str, from);
×
330
}
×
331

332
//----------------------------------------------------------------------
333
void FTextView::deleteRange (int from, int to)
×
334
{
335
  if ( from > to || from >= int(getRows()) || to >= int(getRows()) )
×
336
    throw std::out_of_range("FTextView::deleteRange index out of range");  // Invalid range
×
337

338
  auto iter = data.cbegin();
×
339
  data.erase (iter + from, iter + to + 1);
×
340
}
×
341

342
//----------------------------------------------------------------------
343
void FTextView::onKeyPress (FKeyEvent* ev)
×
344
{
345
  const auto& iter = key_map.find(ev->key());
×
346

347
  if ( iter != key_map.end() )
×
348
  {
349
    iter->second();
×
350
    ev->accept();
×
351
  }
352
}
×
353

354
//----------------------------------------------------------------------
355
void FTextView::onMouseDown (FMouseEvent* ev)
×
356
{
357
  if ( ev->getButton() != MouseButton::Left )
×
358
    return;
×
359

360
  setWidgetFocus(this);
×
361

362
  if ( isLowerRightResizeCorner(ev->getPos()) )
×
363
  {
364
    // Event handover to parent dialog
365
    passResizeCornerEventToDialog(this, *ev);
×
366
    select_click_pos.setPoint(-1, -1);  // Reset click position
×
367
    pass_to_dialog = true;
×
368
    return;
×
369
  }
370

371
  pass_to_dialog = false;
×
372

373
  if ( isSelectable() && isWithinTextBounds(ev->getPos()) )
×
374
  {
375
    select_click_pos.setPoint (convertMouse2TextPos(ev->getPos()));
×
376

377
    if ( isShown() )
×
378
      drawText();
×
379
  }
380
}
381

382
//----------------------------------------------------------------------
383
void FTextView::onMouseUp (FMouseEvent* ev)
×
384
{
385
  if ( isDragging(drag_scroll) )
×
386
    stopDragScroll();
×
387

388
  if ( pass_to_dialog )
×
389
  {
390
    // Event handover to parent dialog
391
    passResizeCornerEventToDialog(this, *ev);
×
392
    select_click_pos.setPoint(-1, -1);  // Reset click position
×
393
    pass_to_dialog = false;
×
394
    return;
×
395
  }
396

397
  // Handle text selection
398
  if ( ev->getButton() == MouseButton::Left )
×
399
    handleMouseWithinListBounds (ev->getPos());
×
400

401
  FPoint select_end_click_pos = convertMouse2TextPos(ev->getPos());
×
402

403
  if ( selection_start.row == static_cast<FTextViewList::size_type>(select_end_click_pos.getY())
×
404
    && selection_start.column == static_cast<FString::size_type>(select_end_click_pos.getX()) )
×
405
    resetSelection();
×
406

407
  select_click_pos.setPoint(-1, -1);  // Reset click position
×
408
  vbar->redraw();
×
409
  hbar->redraw();
×
410

411
  if ( isShown() )
×
412
    drawText();
×
413
}
414

415
//----------------------------------------------------------------------
416
void FTextView::onMouseMove (FMouseEvent* ev)
×
417
{
418
  if ( pass_to_dialog )
×
419
  {
420
    stopDragScroll();
×
421
    // Event handover to parent dialog
422
    passResizeCornerEventToDialog(this, *ev);
×
423
    select_click_pos.setPoint(-1, -1);  // Reset click position
×
424
    return;
×
425
  }
426

427
  // Handle text selection
428
  if ( ev->getButton() == MouseButton::Left )
×
429
    handleMouseWithinListBounds (ev->getPos());
×
430

431
  // Auto-scrolling when dragging mouse outside the widget
432
  handleMouseDragging (ev);
×
433

434
  if ( isShown() )
×
435
    drawText();
×
436
}
437

438
//----------------------------------------------------------------------
439
void FTextView::onMouseDoubleClick (FMouseEvent* ev)
×
440
{
441
  using size_type = std::wstring::size_type;
442

443
  // Word selection exclusion characters
444
  const std::wstring select_exclusion_chars = LR"( !"#$%&'()*+,@~:;=^<>`|[]{}\)";
×
445

446
  if ( ! isSelectable()
×
447
    || ! isWithinTextBounds(ev->getPos())
×
448
    || ev->getButton() != MouseButton::Left )
×
449
    return;
×
450

451
  FPoint click_pos = convertMouse2TextPos(ev->getPos());
×
452
  setSelectionStartInt (click_pos.getY(), click_pos.getX());
×
453
  setSelectionEndInt (click_pos.getY(), click_pos.getX());
×
454

455
  if ( selection_start.row >= getRows()
×
456
    || selection_start.column >= data[selection_start.row].text.getLength() )
×
457
  {
458
    resetSelection();
×
459
    return;
×
460
  }
461

462
  const auto& string = data[selection_start.row].text.toWString();
×
463
  auto start_pos = string.find_last_of( select_exclusion_chars
×
464
                                      , selection_start.column );
465

466
  if ( start_pos == FString::npos )
×
467
    start_pos = 0;
×
468
  else if ( start_pos != selection_start.column )
×
469
    start_pos++;
×
470

471
  auto end_pos = string.find_first_of(select_exclusion_chars, start_pos);
×
472

473
  if ( end_pos == FString::npos )
×
474
    end_pos = string.length();
×
475

476
  selection_start.column = start_pos;
×
477
  selection_end.column = std::max(static_cast<size_type>(0), end_pos - 1);
×
478

479
  if ( isShown() )
×
480
    drawText();
×
481
}
×
482

483
//----------------------------------------------------------------------
484
void FTextView::onWheel (FWheelEvent* ev)
×
485
{
486
  static constexpr int distance = 4;
487
  const auto& wheel = ev->getWheel();
×
488

489
  if ( wheel == MouseWheel::Up )
×
490
    scrollBy (0, -distance);
×
491
  else if ( wheel == MouseWheel::Down )
×
492
    scrollBy (0, distance);
×
493
  else if ( wheel == MouseWheel::Left )
×
494
    scrollBy (-distance, 0);
×
495
  else if ( wheel == MouseWheel::Right )
×
496
    scrollBy (distance, 0);
×
497

498
  // Handle text selection
499
  handleMouseWithinListBounds (ev->getPos());
×
500

501
  if ( isShown() )
×
502
    drawText();
×
503
}
×
504

505
//----------------------------------------------------------------------
506
void FTextView::onTimer (FTimerEvent*)
×
507
{
508
  if ( drag_scroll == DragScrollMode::Leftward )
×
509
    dragLeft();
×
510
  else if ( drag_scroll == DragScrollMode::Rightward )
×
511
    dragRight();
×
512
  else if ( drag_scroll == DragScrollMode::Upward )
×
513
    dragUp();
×
514
  else if ( drag_scroll == DragScrollMode::Downward )
×
515
    dragDown();
×
516

517
  hbar->setValue(xoffset);
×
518
  vbar->setValue(yoffset);
×
519

520
  if ( isShown() )
×
521
  {
522
    drawScrollbars();
×
523
    drawText();
×
524
    forceTerminalUpdate();
×
525
  }
526
}
×
527

528

529
// protected methods of FTextView
530
//----------------------------------------------------------------------
531
void FTextView::initLayout()
×
532
{
533
  nf_offset = FVTerm::getFOutput()->isNewFont() ? 1 : 0;
×
534
  setTopPadding(1);
×
535
  setLeftPadding(1);
×
536
  setBottomPadding(1);
×
537
  setRightPadding(1 + nf_offset);
×
538

539
  if ( data.empty() )
×
540
    return;
×
541

542
  for (const auto& line : data)
×
543
  {
544
    const auto column_width = getColumnWidth(line.text);
×
545
    max_line_width = std::max(column_width, max_line_width);
×
546
  }
547
}
548

549
//----------------------------------------------------------------------
550
void FTextView::adjustSize()
×
551
{
552
  FWidget::adjustSize();
×
553
  const std::size_t width = getWidth();
×
554
  const std::size_t height = getHeight();
×
555
  const auto last_line = int(getRows());
×
556
  const auto max_width = int(max_line_width);
×
557
  const auto xoffset_end = max_width - int(width) - nf_offset - 1;
×
558
  const auto yoffset_end = last_line - int(height) - nf_offset + 2;
×
559
  xoffset = std::max(0, std::min(xoffset, xoffset_end));
×
560
  yoffset = std::max(0, std::min(yoffset, yoffset_end));
×
561

562
  if ( height < 3 )
×
563
    return;
×
564

565
  vbar->setMaximum (getScrollBarMaxVertical());
×
566
  vbar->setPageSize (last_line, int(height) - 2 + nf_offset);
×
567
  vbar->setX (int(width));
×
568
  vbar->setHeight (height - 2 + std::size_t(nf_offset), false);
×
569
  vbar->setValue (yoffset);
×
570
  vbar->resize();
×
571

572
  if ( width < 3 )
×
573
    return;
×
574

575
  hbar->setMaximum (getScrollBarMaxHorizontal());
×
576
  hbar->setPageSize (max_width, int(width) - nf_offset - 2);
×
577
  hbar->setY (int(height));
×
578
  hbar->setWidth (width - 2, false);
×
579
  hbar->setValue (xoffset);
×
580
  hbar->resize();
×
581

582
  if ( isHorizontallyScrollable() )
×
583
    hbar->show();
×
584
  else
585
    hbar->hide();
×
586

587
  if ( isVerticallyScrollable() )
×
588
    vbar->show();
×
589
  else
590
    vbar->hide();
×
591
}
592

593

594
// private methods of FTextView
595
//----------------------------------------------------------------------
596
auto FTextView::getTextHeight() const -> std::size_t
×
597
{
598
  return getHeight() - 2 + std::size_t(nf_offset);
×
599
}
600

601
//----------------------------------------------------------------------
602
auto FTextView::getTextWidth() const -> std::size_t
×
603
{
604
  return getWidth() - 2 - std::size_t(nf_offset);
×
605
}
606

607
//----------------------------------------------------------------------
608
inline auto FTextView::isWithinTextBounds (const FPoint& pos) const -> bool
×
609
{
610
  return pos.getX() > 1
×
611
      && pos.getX() < int(getWidth())
×
612
      && pos.getY() > 1
×
613
      && pos.getY() < int(getHeight());
×
614
}
615

616
//----------------------------------------------------------------------
617
inline auto FTextView::isLowerRightResizeCorner (const FPoint& mouse_pos) const -> bool
×
618
{
619
  // 3 characters in the lower right corner  |
620
  //                                         x
621
  //                                   -----xx
622

623
  return ( (mouse_pos.getX() == int(getWidth()) && mouse_pos.getY() == int(getHeight()) - 1)
×
624
        || ( ( mouse_pos.getX() == int(getWidth()) - 1
×
625
            || mouse_pos.getX() == int(getWidth()) ) && mouse_pos.getY() == int(getHeight()) ) );
×
626
}
627

628
//----------------------------------------------------------------------
629
inline auto FTextView::hasWrongSelectionOrder() const -> bool
×
630
{
631
  bool wrong_column_order = selection_start.row == selection_end.row
×
632
                         && selection_start.column > selection_end.column;
×
633
  bool wrong_row_order = selection_start.row > selection_end.row;
×
634
  return wrong_column_order || wrong_row_order;
×
635
}
636

637
//----------------------------------------------------------------------
638
void FTextView::init()
×
639
{
640
  initScrollbar (vbar, Orientation::Vertical, this, &FTextView::cb_vbarChange);
×
641
  initScrollbar (hbar, Orientation::Horizontal, this, &FTextView::cb_hbarChange);
×
642
  setMinimumSize (FSize{3, 3});
×
643
  FTextView::resetColors();
×
644
  mapKeyFunctions();
×
645
}
×
646

647
//----------------------------------------------------------------------
648
inline void FTextView::mapKeyFunctions()
×
649
{
650
  key_map =
×
651
  {
652
    { FKey::Up        , [this] { scrollBy (0, -1); } },
×
653
    { FKey::Down      , [this] { scrollBy (0, 1); } },
×
654
    { FKey::Left      , [this] { scrollBy (-1, 0); } },
×
655
    { FKey::Right     , [this] { scrollBy (1, 0); } },
×
656
    { FKey::Page_up   , [this] { scrollBy (0, -int(getTextHeight())); } },
×
657
    { FKey::Page_down , [this] { scrollBy (0, int(getTextHeight())); } },
×
658
    { FKey::Home      , [this] { scrollToBegin(); } },
×
659
    { FKey::End       , [this] { scrollToEnd(); } }
×
660
  };
×
661
}
×
662

663
//----------------------------------------------------------------------
664
void FTextView::draw()
×
665
{
666
  setColor();
×
667
  drawBorder();
×
668
  drawScrollbars();
×
669
  drawText();
×
670
  updateStatusbar(this);
×
671
  setCursorPos ({int(getWidth()), int(getHeight())});
×
672
}
×
673

674
//----------------------------------------------------------------------
675
void FTextView::drawBorder()
×
676
{
677
  if ( useFDialogBorder() )
×
678
    return;
×
679

680
  if ( FVTerm::getFOutput()->isMonochron() )
×
681
    setReverse(true);
×
682

683
  const FRect box{FPoint{1, 1}, getSize()};
×
684
  finalcut::drawListBorder (this, box);
×
685

686
  if ( FVTerm::getFOutput()->isMonochron() )
×
687
    setReverse(false);
×
688
}
689

690
//----------------------------------------------------------------------
691
void FTextView::drawScrollbars() const
×
692
{
693
  if ( ! hbar->isShown() && isHorizontallyScrollable() )
×
694
    hbar->show();
×
695
  else
696
    hbar->redraw();
×
697

698
  if ( ! vbar->isShown() && isVerticallyScrollable() )
×
699
    vbar->show();
×
700
  else
701
    vbar->redraw();
×
702
}
×
703

704
//----------------------------------------------------------------------
705
void FTextView::drawText()
×
706
{
707
  if ( canSkipDrawing() )
×
708
    return;
×
709

710
  setColor();
×
711

712
  if ( FVTerm::getFOutput()->isMonochron() )
×
713
    setReverse(true);
×
714

715
  auto num = std::min(getTextHeight(), getRows());
×
716

717
  for (std::size_t y{0}; y < num; y++)  // Line loop
×
718
    printLine (y);
×
719

720
  if ( FVTerm::getFOutput()->isMonochron() )
×
721
    setReverse(false);
×
722
}
723

724
//----------------------------------------------------------------------
725
inline auto FTextView::canSkipDrawing() const -> bool
×
726
{
727
  return data.empty()
×
728
      || getHeight() < 3
×
729
      || getWidth() < 3;
×
730
}
731

732
//----------------------------------------------------------------------
733
inline void FTextView::printLine (std::size_t y)
×
734
{
735
  const std::size_t n = std::size_t(yoffset) + y;
×
736
  const std::size_t pos = std::size_t(xoffset) + 1;
×
737
  const auto text_width = getTextWidth();
×
738
  const FString line(getColumnSubString(data[n].text, pos, text_width));
×
739
  print() << FPoint{2, 2 - nf_offset + int(y)};
×
740
  FVTermBuffer line_buffer{};
×
741
  line_buffer.print(line);
×
742

743
  for (auto&& fchar : line_buffer)  // Column loop
×
744
    if ( ! isPrintable(fchar.ch[0]) )
×
745
      fchar.ch[0] = L'.';
×
746

747
  const auto column_width = getColumnWidth(line);
×
748

749
  if ( column_width <= text_width )
×
750
  {
751
    auto trailing_whitespace = text_width - column_width;
×
752
    line_buffer.print() << FString{trailing_whitespace, L' '};
×
753
  }
754

755
  addHighlighting (line_buffer, data[n].highlight);
×
756
  addSelection (line_buffer, n);
×
757
  print(line_buffer);
×
758
}
×
759

760
//----------------------------------------------------------------------
761
inline void FTextView::addHighlighting ( FVTermBuffer& line_buffer
×
762
                                       , const std::vector<FTextHighlight>& highlight ) const
763
{
764
  for (auto&& hgl : highlight)
×
765
  {
766
    for (std::size_t i{0}; i < hgl.length; i++)
×
767
    {
768
      if ( hgl.index + i < std::size_t(xoffset) )
×
769
        continue;
×
770

771
      auto index = hgl.index + i - std::size_t(xoffset);
×
772

773
      if ( index >= line_buffer.getLength() )
×
774
        break;
×
775

776
      auto& fchar = line_buffer[index];
×
NEW
777
      fchar.color.pair = hgl.attributes.color.pair;
×
778
      fchar.attr = hgl.attributes.attr;
×
779
    }
780
  }
781
}
×
782

783
//----------------------------------------------------------------------
784
inline void FTextView::addSelection (FVTermBuffer& line_buffer, std::size_t n) const
×
785
{
786
  const auto start_row = std::min(selection_start.row, selection_end.row);
×
787
  const auto end_row = std::max(selection_start.row, selection_end.row);
×
788

789
  if ( ! hasSelectedText() || n < start_row || n > end_row )
×
790
    return;
×
791

792
  const bool wrong_order = hasWrongSelectionOrder();
×
793
  const auto start_column = wrong_order ? selection_end.column
×
794
                                        : selection_start.column;
795
  const auto end_column = wrong_order ? selection_start.column
×
796
                                      : selection_end.column;
797
  const auto col_pos = static_cast<std::size_t>(xoffset);
×
798
  const std::size_t start_col = (start_column > col_pos)
×
799
                              ? start_column - col_pos : 0U;
×
800
  const std::size_t end_col = (end_column >= col_pos)
×
801
                            ? end_column - col_pos + 1 : 0U;
×
802
  const auto& wc_text = getColorTheme()->text;
×
803
  const auto has_focus = hasFocus();
×
804
  const auto& selected_fg = has_focus
×
805
                          ? wc_text.selected_focus_fg
806
                          : wc_text.selected_fg;
807
  const auto& selected_bg = has_focus
×
808
                          ? wc_text.selected_focus_bg
809
                          : wc_text.selected_bg;
810
  const std::size_t start_index = (n == start_row) ? start_col : 0U;
×
811
  const std::size_t end_index = (n == end_row)
812
                              ? std::min(end_col, line_buffer.getLength())
×
813
                              : line_buffer.getLength();
×
814

815
  auto select = [&selected_fg, &selected_bg] (auto& fchar)
×
816
  {
NEW
817
    fchar.color.pair = {selected_fg, selected_bg};
×
818
  };
×
819

820
  std::for_each (&line_buffer[start_index], &line_buffer[end_index], select);
×
821
}
822

823
//----------------------------------------------------------------------
824
inline auto FTextView::useFDialogBorder() const -> bool
×
825
{
826
  const auto& parent = getParentWidget();
×
827
  bool use_fdialog_border{false};
×
828

829
  if ( parent
×
830
    && parent->isDialogWidget()
×
831
    && isPaddingIgnored()
×
832
    && getGeometry() == FRect { 1
×
833
                              , 2
834
                              , parent->getWidth()
835
                              , parent->getHeight() - 1} )
×
836
  {
837
    use_fdialog_border = true;
×
838
  }
839

840
  return use_fdialog_border;
×
841
}
842

843
//----------------------------------------------------------------------
844
inline auto FTextView::isPrintable (wchar_t ch) const -> bool
×
845
{
846
  // Check for printable characters
847

848
  const bool utf8 = (FVTerm::getFOutput()->getEncoding() == Encoding::UTF8);
×
849
  return ( (utf8 && finalcut::isPrintable(ch))
×
850
        || (! utf8 && finalcut::isPrintable(char(ch))) );
×
851
}
852

853
//----------------------------------------------------------------------
854
inline auto FTextView::splitTextLines (const FString& str) const -> FStringList
×
855
{
856
  if ( str.isEmpty() )
×
857
  {
858
    FStringList list{};
×
859
    list.emplace_back("");
×
860
    return list;
×
861
  }
×
862

863
  const auto& string = str.rtrim().expandTabs(getFOutput()->getTabstop());
×
864
  return string.split("\n");
×
865
}
×
866

867
//----------------------------------------------------------------------
868
inline void FTextView::processLine (FString&& line, int pos)
×
869
{
870
  line = line.removeBackspaces()
×
871
             .removeDel()
×
872
             .replaceControlCodes()
×
873
             .rtrim();
×
874
  updateHorizontalScrollBar (getColumnWidth(line));
×
875
  data.emplace (data.cbegin() + pos, std::move(line));
×
876
}
×
877

878
//----------------------------------------------------------------------
879
inline auto FTextView::getScrollBarMaxHorizontal() const noexcept -> int
×
880
{
881
  const auto max_width = int(max_line_width);
×
882
  const auto width = int(getWidth());
×
883
  return max_width >= width - nf_offset - 1
×
884
         ? max_width - width + nf_offset + 2
×
885
         : 0;
×
886
}
887

888
//----------------------------------------------------------------------
889
inline auto FTextView::getScrollBarMaxVertical() const noexcept -> int
×
890
{
891
  const auto last_line = int(getRows());
×
892
  const auto height = int(getHeight());
×
893
  return last_line > height - 2 + nf_offset
×
894
         ? last_line - height + 2 - nf_offset
×
895
         : 0;
×
896
}
897

898
//----------------------------------------------------------------------
899
inline void FTextView::updateVerticalScrollBar() const
×
900
{
901
  vbar->setMaximum (getScrollBarMaxVertical());
×
902
  vbar->setPageSize (int(getRows()), int(getTextHeight()));
×
903
  vbar->calculateSliderValues();
×
904

905
  if ( isShown() && ! vbar->isShown() && isVerticallyScrollable() )
×
906
    vbar->show();
×
907

908
  if ( isShown() && vbar->isShown() && ! isVerticallyScrollable() )
×
909
    vbar->hide();
×
910
}
×
911

912
//----------------------------------------------------------------------
913
inline void FTextView::updateHorizontalScrollBar (std::size_t column_width)
×
914
{
915
  if ( column_width <= max_line_width )
×
916
    return;
×
917

918
  max_line_width = column_width;
×
919

920
  if ( column_width <= getTextWidth() )
×
921
    return;
×
922

923
  hbar->setMaximum (getScrollBarMaxHorizontal());
×
924
  hbar->setPageSize (int(max_line_width), int(getTextWidth()));
×
925
  hbar->calculateSliderValues();
×
926

927
  if ( isShown() && isHorizontallyScrollable() )
×
928
    hbar->show();
×
929
}
930

931
//----------------------------------------------------------------------
932
inline auto FTextView::convertMouse2TextPos (const FPoint& pos) const -> FPoint
×
933
{
934
  const FPoint point_delta{ xoffset - getLeftPadding() - 1
×
935
                          , yoffset - getTopPadding() - 1 };
×
936
  return pos + point_delta;
×
937
}
938

939
//----------------------------------------------------------------------
940
void FTextView::handleMouseWithinListBounds (const FPoint& pos)
×
941
{
942
  if ( isSelectable()
×
943
    && select_click_pos != FPoint(-1, -1)
×
944
    && isWithinTextBounds(pos) )
×
945
  {
946
    FPoint select_end_click_pos = convertMouse2TextPos(pos);
×
947

948
    if ( select_end_click_pos == select_click_pos )
×
949
    {
950
      setSelectionStartInt (select_click_pos.getY(), select_click_pos.getX());
×
951
      setSelectionEndInt (select_end_click_pos.getY(), select_end_click_pos.getX());
×
952
    }
953
    else
954
    {
955
      const bool wrong_order = select_click_pos.getY() > select_end_click_pos.getY()
×
956
                            || select_click_pos.getX() > select_end_click_pos.getX();
×
957
      const auto start_column = wrong_order ? select_click_pos.getX() - 1
×
958
                                            : select_click_pos.getX();
×
959
      const auto end_column = wrong_order ? select_end_click_pos.getX()
×
960
                                          : select_end_click_pos.getX() - 1;
×
961
      setSelectionStartInt (select_click_pos.getY(), start_column);
×
962
      setSelectionEndInt (select_end_click_pos.getY(), end_column);
×
963
    }
964

965
    if ( isDragging(drag_scroll) )
×
966
      stopDragScroll();
×
967
  }
968
}
×
969

970
//----------------------------------------------------------------------
971
void FTextView::handleMouseDragging (const FMouseEvent* ev)
×
972
{
973
  // Auto-scrolling when dragging mouse outside the widget
974

975
  if ( ! isSelectable()
×
976
    || select_click_pos == FPoint(-1, -1)
×
977
    || isWithinTextBounds(ev->getPos())
×
978
    || ev->getButton() != MouseButton::Left )
×
979
    return;
×
980

981
  const int mouse_x = ev->getX();
×
982
  const int mouse_y = ev->getY();
×
983

984
  if ( mouse_x < 2 )  // drag left
×
985
    handleLeftDragScroll();
×
986
  else if ( mouse_x >= int(getWidth()) )  // drag right
×
987
    handleRightDragScroll();
×
988
  else if ( mouse_y < 2 )  // drag up
×
989
    handleUpDragScroll();
×
990
  else if ( mouse_y >= int(getHeight()) )  // drag down
×
991
    handleDownDragScroll();
×
992
  else
993
    stopDragScroll();  // no dragging
×
994
}
995

996
//----------------------------------------------------------------------
997
inline void FTextView::handleLeftDragScroll()
×
998
{
999
  if ( xoffset > 0 )
×
1000
  {
1001
    drag_scroll = DragScrollMode::Leftward;
×
1002
    delOwnTimers();
×
1003
    addTimer(scroll_repeat);
×
1004
  }
1005

1006
  if ( xoffset == 0 )
×
1007
  {
1008
    delOwnTimers();
×
1009
    drag_scroll = DragScrollMode::None;
×
1010
  }
1011
}
×
1012

1013
//----------------------------------------------------------------------
1014
inline void FTextView::handleRightDragScroll()
×
1015
{
1016
  const auto xoffset_end = int(max_line_width - getTextWidth());
×
1017

1018
  if ( xoffset < xoffset_end )
×
1019
  {
1020
    drag_scroll = DragScrollMode::Rightward;
×
1021
    delOwnTimers();
×
1022
    addTimer(scroll_repeat);
×
1023
  }
1024

1025
  if ( xoffset == xoffset_end )
×
1026
  {
1027
    delOwnTimers();
×
1028
    drag_scroll = DragScrollMode::None;
×
1029
  }
1030
}
×
1031

1032
//----------------------------------------------------------------------
1033
inline void FTextView::handleUpDragScroll()
×
1034
{
1035
  if ( yoffset > 0 )
×
1036
  {
1037
    drag_scroll = DragScrollMode::Upward;
×
1038
    delOwnTimers();
×
1039
    addTimer(scroll_repeat);
×
1040
  }
1041

1042
  if ( yoffset == 0 )
×
1043
  {
1044
    delOwnTimers();
×
1045
    drag_scroll = DragScrollMode::None;
×
1046
  }
1047
}
×
1048

1049
//----------------------------------------------------------------------
1050
inline void FTextView::handleDownDragScroll()
×
1051
{
1052
  const auto yoffset_end = int(getRows() - getTextHeight());
×
1053

1054
  if ( yoffset < yoffset_end )
×
1055
  {
1056
    drag_scroll = DragScrollMode::Downward;
×
1057
    delOwnTimers();
×
1058
    addTimer(scroll_repeat);
×
1059
  }
1060

1061
  if ( yoffset == yoffset_end )
×
1062
  {
1063
    delOwnTimers();
×
1064
    drag_scroll = DragScrollMode::None;
×
1065
  }
1066
}
×
1067

1068
//----------------------------------------------------------------------
1069
void FTextView::dragLeft()
×
1070
{
1071
  if ( xoffset > 0 )
×
1072
  {
1073
    xoffset--;
×
1074
    selection_end.column--;
×
1075
  }
1076

1077
  if ( xoffset == 0 )
×
1078
  {
1079
    drag_scroll = DragScrollMode::None;
×
1080
    stopDragScroll();
×
1081
    return;
×
1082
  }
1083
}
1084

1085
//----------------------------------------------------------------------
1086
void FTextView::dragRight()
×
1087
{
1088
  const auto xoffset_end = int(max_line_width - getTextWidth());
×
1089

1090
  if ( xoffset < xoffset_end )
×
1091
  {
1092
    xoffset++;
×
1093
    selection_end.column++;
×
1094
  }
1095

1096
  if ( xoffset == xoffset_end )
×
1097
  {
1098
    drag_scroll = DragScrollMode::None;
×
1099
    stopDragScroll();
×
1100
    return;
×
1101
  }
1102
}
1103

1104
//----------------------------------------------------------------------
1105
void FTextView::dragUp()
×
1106
{
1107
  if ( yoffset > 0 )
×
1108
  {
1109
    yoffset--;
×
1110
    const auto start_row = std::size_t(yoffset);
×
1111
    setSelectionEnd (start_row, 0);
×
1112
  }
1113

1114
  if ( yoffset == 0 )
×
1115
  {
1116
    drag_scroll = DragScrollMode::None;
×
1117
    stopDragScroll();
×
1118
    return;
×
1119
  }
1120
}
1121

1122
//----------------------------------------------------------------------
1123
void FTextView::dragDown()
×
1124
{
1125
  const auto yoffset_end = int(getRows() - getTextHeight());
×
1126

1127
  if ( yoffset < yoffset_end )
×
1128
  {
1129
    yoffset++;
×
1130
    const auto end_column = max_line_width - 1;
×
1131
    const std::size_t end_row = std::size_t(yoffset)
×
1132
                              + std::min(getTextHeight(), getRows()) - 1;
×
1133
    setSelectionEnd (end_row, end_column);
×
1134
  }
1135

1136
  if ( yoffset == yoffset_end )
×
1137
  {
1138
    drag_scroll = DragScrollMode::None;
×
1139
    stopDragScroll();
×
1140
    return;
×
1141
  }
1142
}
1143

1144
//----------------------------------------------------------------------
1145
void FTextView::stopDragScroll()
×
1146
{
1147
  delOwnTimers();
×
1148
  drag_scroll = DragScrollMode::None;
×
1149
}
×
1150

1151
//----------------------------------------------------------------------
1152
void FTextView::processChanged() const
×
1153
{
1154
  emitCallback("changed");
×
1155
}
×
1156

1157
//----------------------------------------------------------------------
1158
void FTextView::changeOnResize() const
×
1159
{
1160
  const std::size_t width  = getWidth();
×
1161
  const std::size_t height = getHeight();
×
1162

1163
  if ( FVTerm::getFOutput()->isNewFont() )
×
1164
  {
1165
    vbar->setGeometry (FPoint{int(width), 1}, FSize{2, height - 1});
×
1166
    hbar->setGeometry (FPoint{1, int(height)}, FSize{width - 2, 1});
×
1167
  }
1168
  else
1169
  {
1170
    vbar->setGeometry (FPoint{int(width), 2}, FSize{1, height - 2});
×
1171
    hbar->setGeometry (FPoint{2, int(height)}, FSize{width - 2, 1});
×
1172
  }
1173

1174
  vbar->resize();
×
1175
  hbar->resize();
×
1176
}
×
1177

1178
//----------------------------------------------------------------------
1179
inline auto FTextView::shouldUpdateScrollbar (FScrollbar::ScrollType scroll_type) const -> bool
×
1180
{
1181
  return scroll_type >= FScrollbar::ScrollType::StepBackward;
×
1182
}
1183

1184
//----------------------------------------------------------------------
1185
inline auto FTextView::getVerticalScrollDistance (const FScrollbar::ScrollType scroll_type) const -> int
×
1186
{
1187
  if ( scroll_type == FScrollbar::ScrollType::PageBackward
×
1188
    || scroll_type == FScrollbar::ScrollType::PageForward )
×
1189
  {
1190
    return int(getClientHeight());
×
1191
  }
1192

1193
  return 1;
×
1194
}
1195

1196
//----------------------------------------------------------------------
1197
inline auto FTextView::getHorizontalScrollDistance (const FScrollbar::ScrollType scroll_type) const -> int
×
1198
{
1199
  if ( scroll_type == FScrollbar::ScrollType::PageBackward
×
1200
    || scroll_type == FScrollbar::ScrollType::PageForward )
×
1201
  {
1202
    return int(getClientWidth());
×
1203
  }
1204

1205
  return 1;
×
1206
}
1207

1208
//----------------------------------------------------------------------
1209
void FTextView::cb_vbarChange (const FWidget*)
×
1210
{
1211
  const auto scroll_type = vbar->getScrollType();
×
1212
  update_scrollbar = shouldUpdateScrollbar(scroll_type);
×
1213
  static constexpr int wheel_distance = 4;
1214
  int distance = getVerticalScrollDistance(scroll_type);
×
1215

1216
  switch ( scroll_type )
×
1217
  {
1218
    case FScrollbar::ScrollType::PageBackward:
×
1219
    case FScrollbar::ScrollType::StepBackward:
1220
      scrollBy (0, -distance);
×
1221
      break;
×
1222

1223
    case FScrollbar::ScrollType::PageForward:
×
1224
    case FScrollbar::ScrollType::StepForward:
1225
      scrollBy (0, distance);
×
1226
      break;
×
1227

1228
    case FScrollbar::ScrollType::Jump:
×
1229
      scrollToY (vbar->getValue());
×
1230
      break;
×
1231

1232
    case FScrollbar::ScrollType::WheelUp:
×
1233
    case FScrollbar::ScrollType::WheelLeft:
1234
      scrollBy (0, -wheel_distance);
×
1235
      break;
×
1236

1237
    case FScrollbar::ScrollType::WheelDown:
×
1238
    case FScrollbar::ScrollType::WheelRight:
1239
      scrollBy (0, wheel_distance);
×
1240
      break;
×
1241

1242
    default:
×
1243
      throw std::invalid_argument{"Invalid scroll type"};
×
1244
  }
1245

1246
  update_scrollbar = true;
×
1247
}
×
1248

1249
//----------------------------------------------------------------------
1250
void FTextView::cb_hbarChange (const FWidget*)
×
1251
{
1252
  const auto scroll_type = hbar->getScrollType();
×
1253
  update_scrollbar = shouldUpdateScrollbar(scroll_type);
×
1254
  static constexpr int wheel_distance = 4;
1255
  int distance = getHorizontalScrollDistance(scroll_type);
×
1256

1257
  switch ( scroll_type )
×
1258
  {
1259
    case FScrollbar::ScrollType::PageBackward:
×
1260
    case FScrollbar::ScrollType::StepBackward:
1261
      scrollBy (-distance, 0);
×
1262
      break;
×
1263

1264
    case FScrollbar::ScrollType::PageForward:
×
1265
    case FScrollbar::ScrollType::StepForward:
1266
      scrollBy (distance, 0);
×
1267
      break;
×
1268

1269
    case FScrollbar::ScrollType::Jump:
×
1270
      scrollToX (hbar->getValue());
×
1271
      break;
×
1272

1273
    case FScrollbar::ScrollType::WheelUp:
×
1274
    case FScrollbar::ScrollType::WheelLeft:
1275
      scrollBy (-wheel_distance, 0);
×
1276
      break;
×
1277

1278
    case FScrollbar::ScrollType::WheelDown:
×
1279
    case FScrollbar::ScrollType::WheelRight:
1280
      scrollBy (wheel_distance, 0);
×
1281
      break;
×
1282

1283
    default:
×
1284
      throw std::invalid_argument{"Invalid scroll type"};
×
1285
  }
1286

1287
  update_scrollbar = true;
×
1288
}
×
1289

1290
}  // namespace finalcut
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc