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

gansm / finalcut / #772

15 Feb 2026 12:08AM UTC coverage: 69.046% (-0.03%) from 69.073%
#772

push

travis-ci

gansm
More iterators and further code improvements

77 of 129 new or added lines in 8 files covered. (59.69%)

3 existing lines in 3 files now uncovered.

37632 of 54503 relevant lines covered (69.05%)

250.4 hits per line

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

0.87
/final/fwidget_functions.cpp
1
/***********************************************************************
2
* fwidget_functions.cpp - FWidget helper functions                     *
3
*                                                                      *
4
* This file is part of the FINAL CUT widget toolkit                    *
5
*                                                                      *
6
* Copyright 2019-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 <algorithm>
24
#include <array>
25
#include <memory>
26

27
#include "final/dialog/fdialog.h"
28
#include "final/fapplication.h"
29
#include "final/fevent.h"
30
#include "final/fwidgetcolors.h"
31
#include "final/fwidget_functions.h"
32
#include "final/fwidget.h"
33
#include "final/vterm/fcolorpair.h"
34
#include "final/vterm/fstyle.h"
35
#include "final/widget/fstatusbar.h"
36

37
namespace finalcut
38
{
39

40
namespace internal
41
{
42

43
constexpr auto getPrintTransMask() noexcept -> uInt32
44
{
45
  FCharAttribute mask{};
46
  mask.transparent = true;
47
  mask.color_overlay = true;
48
  mask.inherit_background = true;
49
  mask.no_changes = true;
50
  mask.printed = true;
51
  return FCharAttribute_to_uInt32(mask);
52
}
53

54
struct var
55
{
56
  static constexpr auto print_trans_mask = getPrintTransMask();
57
};
58

59
constexpr uInt32 var::print_trans_mask;
60

61
}  // namespace internal
62

63
//----------------------------------------------------------------------
64
struct TransparentShadowData
65
{
66
  // Using-declaration
67
  using FTermArea = FVTerm::FTermArea;
68

69
  // Data members
70
  FTermArea& area;
71
  uInt       width{};
72
  uInt       height{};
73
  uInt       shadow_width{};
74
  uInt       shadow_height{};
75
  FChar      transparent_char{};
76
  FChar      color_overlay_char{};
77
  FChar*     area_ptr{};
78
};
79

80

81
// Function forward declarations
82
//----------------------------------------------------------------------
83
void drawRightShadow (TransparentShadowData&);
84
void drawBottomShadow (TransparentShadowData&);
85

86

87
// FWidget non-member functions
88
//----------------------------------------------------------------------
89
auto isFocusNextKey (const FKey key) noexcept -> bool
×
90
{
91
  return ( key == FKey::Tab
92
        || key == FKey::Right
×
93
        || key == FKey::Down );
×
94
}
95

96
//----------------------------------------------------------------------
97
auto isFocusPrevKey (const FKey key) noexcept -> bool
×
98
{
99
  return ( key == FKey::Back_tab
100
        || key == FKey::Left
×
101
        || key == FKey::Up );
×
102
}
103

104
//----------------------------------------------------------------------
105
auto isDialogMenuKey (const FKey key) noexcept -> bool
×
106
{
107
  return ( key == FKey::Ctrl_caret     // Ctrl+^ (Ctrl+6)
108
        || key == FKey::F22            // Shift+F10
×
109
        || key == FKey::Shift_menu );  // Shift+Menu
×
110
}
111

112
//----------------------------------------------------------------------
113
auto isEnterKey (const FKey key) noexcept -> bool
×
114
{
115
  return ( key == FKey::Return
116
        || key == FKey::Enter );
×
117
}
118

119
//----------------------------------------------------------------------
120
auto isEscapeKey (const FKey key) noexcept -> bool
×
121
{
122
  return ( key == FKey::Escape
123
        || key == FKey::Escape_mintty );
×
124
}
125

126
//----------------------------------------------------------------------
127
auto isExpandComboBoxKey (const FKey key) noexcept -> bool
×
128
{
129
  return ( key == FKey::F4
130
        || key == FKey::Meta_down
×
131
        || key == FKey::Ctrl_down );
×
132
}
133

134
//----------------------------------------------------------------------
135
auto isCollapseComboBoxKey (const FKey key) noexcept -> bool
×
136
{
137
  return ( key == FKey::Meta_up
138
        || key == FKey::Ctrl_up
×
139
        || isEscapeKey(key) );
×
140
}
141

142
//----------------------------------------------------------------------
143
auto getFirstFocusableWidget (const FObject::FObjectList& list) -> FWidget*
×
144
{
145
  if ( list.empty() )
×
146
    return nullptr;
×
147

148
  auto iter = list.cbegin();
×
149

150
  while ( iter != list.cend() )
×
151
  {
152
    if ( (*iter)->isWidget() )
×
153
    {
154
      auto child = static_cast<FWidget*>(*iter);
×
155

156
      if ( child->isEnabled() && child->acceptFocus() )
×
157
        return child;
×
158
    }
159

160
    ++iter;
×
161
  }
162

163
  return nullptr;
×
164
}
165

166
//----------------------------------------------------------------------
167
auto getLastFocusableWidget (const FObject::FObjectList& list) -> FWidget*
×
168
{
169
  if ( list.empty() )
×
170
    return nullptr;
×
171

172
  auto iter  = list.cend();
×
173

174
  do
175
  {
176
    --iter;
×
177

178
    if ( ! (*iter)->isWidget() )
×
179
      continue;
×
180

181
    auto child = static_cast<FWidget*>(*iter);
×
182

183
    if ( child->isEnabled() && child->acceptFocus() )
×
184
      return child;
×
185
  }
186
  while ( iter != list.cbegin() );
×
187

188
  return nullptr;
×
189
}
190

191
//----------------------------------------------------------------------
192
auto isInFWidgetList (const FWidget::FWidgetList* list, const FWidget* obj) -> bool
2✔
193
{
194
  if ( ! list || ! obj )
2✔
195
    return false;
×
196

197
  return std::any_of ( list->cbegin(), list->cend()
2✔
198
                     , [&obj] (const auto& w) { return w == obj; } );
3✔
199
}
200

201
//----------------------------------------------------------------------
202
auto getFApplication() noexcept -> FApplication*
×
203
{
204
  return FApplication::getApplicationObject();
×
205
}
206

207
//----------------------------------------------------------------------
208
auto getAlignOffset ( Align alignment
×
209
                    , const std::size_t width
210
                    , const std::size_t length ) noexcept -> std::size_t
211
{
212
  if ( alignment == Align::Center )
×
213
  {
214
    if ( length < width )
×
215
      return (width - length) / 2;
×
216
  }
217
  else if ( alignment == Align::Right && length < width )
×
218
  {
219
    return width - length;
×
220
  }
221

222
  return 0;
×
223
}
224

225
//----------------------------------------------------------------------
226
auto getHotkey (const FString& text) -> FKey
×
227
{
228
  // Returns the hotkey character from a string
229
  // e.g. "E&xit" returns 'x'
230

231
  if ( text.isEmpty() )
×
232
    return FKey::None;
×
233

234
  std::size_t i{0};
×
235
  const std::size_t length = text.getLength();
×
236

237
  while ( i < length )
×
238
  {
239
    try
240
    {
241
      if ( i + 1 < length && text[i] == '&' )
×
242
      {
243
        i++;
×
244
        return FKey(text[i]);
×
245
      }
246
    }
247
    catch (const std::out_of_range&)
×
248
    {
249
      return FKey::None;
×
250
    }
×
251

252
    i++;
×
253
  }
254

255
  return FKey::None;
×
256
}
257

258
//----------------------------------------------------------------------
259
auto getHotkeyPos (const FString& src, FString& dest) -> std::size_t
×
260
{
261
  // Find hotkey position in string
262
  // + generate a new string without the '&'-sign
263

264
  static constexpr auto NOT_SET = static_cast<std::size_t>(-1);
265
  std::size_t hotkeypos{NOT_SET};
×
266
  std::size_t i{0};
×
267

268
  for (auto&& ch : src)
×
269
  {
270
    if ( ch == L'&' && hotkeypos == NOT_SET && src.getLength() != i + 1 )
×
271
      hotkeypos = i;
×
272
    else
273
      dest += ch;
×
274

275
    i++;
×
276
  }
277

278
  return hotkeypos;
×
279
}
280
//----------------------------------------------------------------------
281
void setHotkeyViaString (FWidget* w, const FString& text)
×
282
{
283
  // Set hotkey accelerator via string
284

285
  if ( ! w )
×
286
    return;
×
287

288
  FKey hotkey = getHotkey(text);
×
289

290
  if ( hotkey > 0xff00 && hotkey < 0xff5f )  // full-width character
×
291
    hotkey -= 0xfee0;
×
292

293
  if ( hotkey != FKey::None )
×
294
  {
295
    if ( std::isalpha(int(hotkey)) || std::isdigit(int(hotkey)) )
×
296
    {
297
      w->addAccelerator (FKey(std::tolower(int(hotkey))));
×
298
      w->addAccelerator (FKey(std::toupper(int(hotkey))));
×
299
      // Meta + hotkey
300
      w->addAccelerator (FKey::Meta_offset + FKey(std::tolower(int(hotkey))));
×
301
    }
302
    else
303
      w->addAccelerator (hotkey);
×
304
  }
305
  else
306
    w->delAccelerator();
×
307
}
308

309
//----------------------------------------------------------------------
310
void setWidgetFocus (FWidget* widget)
×
311
{
312
  if ( ! widget || widget->hasFocus() )
×
313
    return;
×
314

315
  auto focused_widget = FWidget::getFocusWidget();
×
316
  widget->setFocus();
×
317

318
  if ( focused_widget && focused_widget->isWidget() )  // old focused widget
×
319
    focused_widget->redraw();
×
320

321
  widget->redraw();
×
322
  drawStatusBarMessage();
×
323
}
324

325
//----------------------------------------------------------------------
326
void setWidgetShadow (FWidget* w, bool enable)
×
327
{
328
  if ( enable
×
329
    && FVTerm::getFOutput()->getEncoding() != Encoding::VT100
×
330
    && FVTerm::getFOutput()->getEncoding() != Encoding::ASCII )
×
331
  {
332
    w->setFlags().shadow.shadow = true;
×
333
    w->setShadowSize(FSize{1, 1});
×
334
  }
335
  else
336
  {
337
    w->setFlags().shadow.shadow = false;
×
338
    w->setShadowSize(FSize{0, 0});
×
339
  }
340
}
×
341

342
//----------------------------------------------------------------------
343
void passResizeCornerEventToDialog (const FWidget* w, const FMouseEvent& ev)
×
344
{
345
  // Pass mouse event to the parent widget
346

347
  auto parent = w->getParentWidget();
×
348

349
  if ( ! parent || ! parent->isDialogWidget() )
×
350
    return;
×
351

352
  const auto& dialog = static_cast<FDialog*>(parent);
×
353

354
  if ( ! dialog->isResizeable() || dialog->isZoomed() )
×
355
    return;
×
356

357
  const auto& type = ev.getType();
×
358
  const auto& tpos = ev.getTermPos();
×
359
  const auto& par = parent->termToWidgetPos(tpos);
×
360
  const auto btn = ev.getButton();
×
361
  const auto& new_ev = \
362
      std::make_shared<FMouseEvent>(type, par, tpos, btn);
×
363
  FApplication::sendEvent (parent, new_ev.get());
×
364
}
×
365

366
//----------------------------------------------------------------------
367
void drawShadow (FWidget* w)
×
368
{
369
  if ( FVTerm::getFOutput()->isMonochron() && ! w->flags.shadow.trans_shadow )
×
370
    return;
×
371

372
  if ( (FVTerm::getFOutput()->getEncoding() == Encoding::VT100 && ! w->flags.shadow.trans_shadow)
×
373
    || (FVTerm::getFOutput()->getEncoding() == Encoding::ASCII && ! w->flags.shadow.trans_shadow) )
×
374
  {
375
    clearBlockShadow(w);
×
376
    return;
×
377
  }
378

379
  if ( w->flags.shadow.trans_shadow )
×
380
    drawTransparentShadow (w);  // transparent shadow
×
381
  else if ( w->flags.shadow.shadow )
×
382
    drawBlockShadow (w);        // non-transparent shadow
×
383
}
384

385
//----------------------------------------------------------------------
386
void drawTransparentShadow (FWidget* w)
×
387
{
388
  // transparent shadow
389

390
  if ( ! w || ! w->getPrintArea() )
×
391
    return;
×
392

393
  auto& area = *w->getPrintArea();
×
394
  const auto& wc_shadow = FWidget::getColorTheme()->shadow;
×
395

396
  TransparentShadowData data
×
397
  {
398
    area,
399
    uInt(area.size.width),
×
400
    uInt(area.size.height),
×
401
    uInt(area.shadow.width),
×
402
    uInt(area.shadow.height),
×
403
    {
404
      { L'\0',  L'\0', L'\0', L'\0', L'\0' },
405
      { L'\0', L'\0', L'\0', L'\0', L'\0' },
406
      { { FColor::Default, FColor::Default } },
407
      { { 0x00, 0x20, 0x00, 0x00} }  // byte 0..3 (byte 1 = 0x32 = transparent)
408
    },
409
    {
410
      { L'\0', L'\0', L'\0', L'\0', L'\0' },
411
      { L'\0', L'\0', L'\0', L'\0', L'\0' },
412
      { { wc_shadow.fg, wc_shadow.bg } },
×
413
      { { 0x00, 0x40, 0x00, 0x00} }  // byte 0..3 (byte 1 = 0x64 = color_overlay)
414
    },
415
    &area.getFChar(area.size.width, 0)
×
416
  };
×
417

418
  drawRightShadow(data);
×
419
  drawBottomShadow(data);
×
420
  area.has_changes = true;
×
421
  // Update row changes
422
  auto last_row = uInt(data.height + data.shadow_height - 1);
×
423
  area.changes_in_row = {uInt(0), last_row};
×
424

425
  if ( FVTerm::getFOutput()->isMonochron() )
×
426
    w->setReverse(false);
×
427
}
428

429
//----------------------------------------------------------------------
430
inline void drawRightShadow (TransparentShadowData& d)
×
431
{
NEW
432
  if ( d.shadow_width == 0 )
×
NEW
433
    return;
×
434

435
  // Draw right shadow
NEW
436
  const auto s_width = d.shadow_width;
×
NEW
437
  const auto width = d.width;
×
NEW
438
  const auto height = d.height;
×
NEW
439
  auto* ptr = d.area_ptr;
×
NEW
440
  auto changes_in_line = d.area.changes_in_line;
×
NEW
441
  auto& changes_in_1st_line = changes_in_line[0];
×
442

NEW
443
  std::fill (ptr, std::next(ptr, s_width), d.transparent_char);
×
NEW
444
  changes_in_1st_line.xmin = std::min(changes_in_1st_line.xmin, width);
×
NEW
445
  changes_in_1st_line.xmax = width + s_width - 1;
×
NEW
446
  changes_in_1st_line.trans_count += s_width;
×
NEW
447
  const auto total_width = width + s_width;
×
448

NEW
449
  for (std::size_t y{1}; y < height; y++)
×
450
  {
NEW
451
    ptr = std::next(ptr, total_width);
×
NEW
452
    auto& changes = changes_in_line[y];
×
NEW
453
    changes.xmin = std::min(d.area.changes_in_line[y].xmin, width);
×
NEW
454
    changes.xmax = width + s_width - 1;
×
NEW
455
    changes.trans_count += s_width;
×
NEW
456
    std::fill (ptr, std::next(ptr, s_width), d.color_overlay_char);
×
457
  }
458

NEW
459
  d.area_ptr = std::next(ptr, s_width);
×
UNCOV
460
}
×
461

462
//----------------------------------------------------------------------
463
inline void drawBottomShadow (TransparentShadowData& d)
×
464
{
NEW
465
  if ( d.shadow_height == 0 )
×
NEW
466
    return;
×
467

468
  // Draw bottom shadow
NEW
469
  const auto s_width = d.shadow_width;
×
NEW
470
  const auto width = d.width;
×
NEW
471
  const auto total_width = width + s_width;
×
NEW
472
  const auto xmax = total_width - 1;
×
NEW
473
  const auto s_height = d.shadow_height;
×
NEW
474
  const auto start_y = d.height;
×
NEW
475
  auto changes_in_line = d.area.changes_in_line;
×
NEW
476
  auto* ptr = d.area_ptr;
×
477

NEW
478
  for (std::size_t i{0}; i < s_height; i++)
×
479
  {
NEW
480
    const auto y = start_y + i;
×
NEW
481
    auto& changes = changes_in_line[y];
×
NEW
482
    changes.xmin = 0;
×
NEW
483
    changes.xmax = xmax;
×
NEW
484
    changes.trans_count += total_width;
×
NEW
485
    std::fill (ptr, std::next(ptr, s_width), d.transparent_char);
×
NEW
486
    ptr = std::next(ptr, s_width);
×
NEW
487
    std::fill (ptr, std::next(ptr, width), d.color_overlay_char);
×
NEW
488
    ptr = std::next(ptr, width);
×
489
  }
490
}
×
491

492
//----------------------------------------------------------------------
493
void drawBlockShadow (FWidget* w)
×
494
{
495
  // non-transparent shadow
496

497
  if ( ! w || ! FVTerm::getFOutput()->hasShadowCharacter() )
×
498
    return;
×
499

500
  const auto& wc_shadow = FWidget::getColorTheme()->shadow;
×
501

502
  std::array<FChar, 4> shadow_char
×
503
  {{
504
    {
505
      { wchar_t(UniChar::LowerHalfBlock),  L'\0', L'\0', L'\0', L'\0' },  // ▄
506
      { L'\0', L'\0', L'\0', L'\0', L'\0' },
507
      { { wc_shadow.bg, FColor::Default } },
×
508
      { { 0x00, 0x00, 0x08, 0x00} }  // byte 0..3 (byte 2 = 0x08 = char_width 1)
509
    },
510
    {
511
      { wchar_t(UniChar::FullBlock),  L'\0', L'\0', L'\0', L'\0' },  // █
512
      { L'\0', L'\0', L'\0', L'\0', L'\0' },
513
      { { wc_shadow.bg, FColor::Default } },
×
514
      { { 0x00, 0x00, 0x08, 0x00} }  // byte 0..3 (byte 2 = 0x08 = char_width 1)
515
    },
516
    {
517
      { L' ',  L'\0', L'\0', L'\0', L'\0' },  // ' '
518
      { L'\0', L'\0', L'\0', L'\0', L'\0' },
519
      { { FColor::Default, FColor::Default } },
520
      { { 0x00, 0x00, 0x08, 0x00} }  // byte 0..3 (byte 2 = 0x08 = char_width 1)
521
    },
522
    {
523
      { wchar_t(UniChar::UpperHalfBlock),  L'\0', L'\0', L'\0', L'\0' },  // ▄
524
      { L'\0', L'\0', L'\0', L'\0', L'\0' },
525
      { { wc_shadow.bg, FColor::Default } },
×
526
      { { 0x00, 0x00, 0x08, 0x00} }  // byte 0..3 (byte 2 = 0x08 = char_width 1)
527
    }
528
  }};
×
529

530
  if ( w->isWindowWidget() )
×
531
  {
532
    shadow_char[0].attr.bit.inherit_background = true;
×
533
    shadow_char[1].attr.bit.inherit_background = true;
×
534
    shadow_char[2].attr.bit.transparent = true;
×
535
    shadow_char[3].attr.bit.inherit_background = true;
×
536
  }
537
  else if ( auto p = w->getParentWidget() )
×
538
  {
539
    shadow_char[0].color.pair.bg = p->getBackgroundColor();
×
540
    shadow_char[1].color.pair.bg = p->getBackgroundColor();
×
541
    shadow_char[2].color.pair.bg = p->getBackgroundColor();
×
542
    shadow_char[3].color.pair.bg = p->getBackgroundColor();
×
543
  }
544

545
  drawGenericBlockShadow (w, shadow_char);
×
546
}
547

548
//----------------------------------------------------------------------
549
void clearBlockShadow (FWidget* w)
×
550
{
551
  if ( ! w || ! FVTerm::getFOutput()->hasShadowCharacter() )
×
552
    return;
×
553

554
  FChar spacer_char
×
555
  {
556
    { L' ',  L'\0', L'\0', L'\0', L'\0' },  // ' '
557
    { L'\0', L'\0', L'\0', L'\0', L'\0' },
558
    { { FColor::Default, FColor::Default } },
559
    { { 0x00, 0x00, 0x08, 0x00} }  // byte 0..3 (byte 2 = 0x08 = char_width 1)
560
  };
561

562
  if ( w->isWindowWidget() )
×
563
    spacer_char.attr.bit.transparent = true;
×
564
  else if ( auto p = w->getParentWidget() )
×
565
    spacer_char.color.pair.bg = p->getBackgroundColor();
×
566

567
  const std::array<FChar, 4> shadow_char
568
  {
569
    {spacer_char, spacer_char, spacer_char, spacer_char}
570
  };
×
571

572
  drawGenericBlockShadow (w, shadow_char);
×
573
}
574

575
//----------------------------------------------------------------------
576
struct GenericBlockShadowData
577
{
578
  // Constructor
579
  GenericBlockShadowData (FWidget* w, const std::array<FChar, 4>& chars)
×
580
    : shadow_char{chars}
×
581
    , area{*w->getPrintArea()}
×
582
    , is_window{w->isWindowWidget()}
×
583
    , width{is_window ? uInt(area.size.width) : uInt(w->getWidth())}
×
584
    , height{is_window ? uInt(area.size.height) : uInt(w->getHeight())}
×
585
    , shadow_width{uInt(area.shadow.width)}
×
586
    , shadow_height{uInt(area.shadow.height)}
×
587
    , x_offset{uInt(w->woffset.getX1() + w->getX() - area.position.x - 1)}
×
588
    , y_offset{uInt(w->woffset.getY1() + w->getY() - area.position.y - 1)}
×
589
    , shadow_x{x_offset + width}
×
590
    , shadow_y{y_offset + height}
×
591
  { }
×
592

593
  inline void updateChanges (uInt y, uInt xmin, uInt xmax, uInt tc = 0)
×
594
  {
595
    auto& changes = area.changes_in_line[y];
×
596
    changes.xmin = std::min(changes.xmin, xmin);
×
597
    changes.xmax = std::max(changes.xmax, xmax);
×
598
    changes.trans_count += tc + 1;
×
599
  }
×
600

601
  // Data members
602
  const std::array<FChar, 4>& shadow_char{};
603
  FVTerm::FTermArea& area;
604
  const bool is_window{false};
605
  const uInt width{0};
606
  const uInt height{0};
607
  const uInt shadow_width{0};
608
  const uInt shadow_height{0};
609
  const uInt x_offset{0};
610
  const uInt y_offset{0};
611
  const uInt shadow_x{0};  // Shadow column (right)
612
  const uInt shadow_y{0};  // Shadow row (bottom)
613
};
614

615
//----------------------------------------------------------------------
616
void drawGenericBlockShadow ( FWidget* w
×
617
                            , const std::array<FChar, 4>& shadow_char )
618
{
619
  if ( ! w
×
620
    || ! w->getPrintArea()
×
621
    || ! FVTerm::getFOutput()->hasShadowCharacter() )
×
622
    return;
×
623

624
  GenericBlockShadowData bsd(w, shadow_char);
×
625

626
  if ( bsd.is_window && (bsd.shadow_width < 1 || bsd.shadow_height < 1) )
×
627
    return;
×
628

629
  // Draw the top-right shadow
630
  auto area_iter = bsd.area.getFCharIterator(int(bsd.shadow_x), int(bsd.y_offset));
×
631
  *area_iter = bsd.shadow_char[0];  // ▄ (top-right corner)
×
632
  bsd.updateChanges(bsd.y_offset, bsd.shadow_x, bsd.shadow_x);
×
633

634
  // Draw the right-side shadow
635
  for (uInt y = bsd.y_offset + 1; y < bsd.shadow_y; y++)
×
636
  {
637
    area_iter = bsd.area.getFCharIterator(int(bsd.shadow_x), int(y));
×
638
    *area_iter = bsd.shadow_char[1];  // █
×
639
    bsd.updateChanges(y, bsd.shadow_x, bsd.shadow_x);
×
640
  }
641

642
  // Draw the bottom shadow
643
  area_iter = bsd.area.getFCharIterator(int(bsd.x_offset), int(bsd.shadow_y));
×
644
  *area_iter = bsd.shadow_char[2];  // ' ' (bottom-left corner)
×
645
  ++area_iter;
×
646
  // Fill the horizontal shadow
647
  std::fill (area_iter, area_iter + bsd.width, bsd.shadow_char[3]);  // ▀
×
648
  bsd.updateChanges(bsd.shadow_y, bsd.x_offset, bsd.shadow_x, bsd.width);
×
649
  // Update row changes
650
  auto& changes_in_row = bsd.area.changes_in_row;
×
651
  changes_in_row.ymin = std::min(changes_in_row.ymin, bsd.y_offset);
×
652
  changes_in_row.ymax = std::max(changes_in_row.ymax, bsd.shadow_y);
×
653
}
654

655
//----------------------------------------------------------------------
656
inline void setWidgetColors (const FWidget* w)
×
657
{
658
  const auto& wc_dialog = FWidget::getColorTheme()->dialog;
×
659

660
  if ( auto p = w->getParentWidget() )
×
661
    w->setColor (wc_dialog.fg, p->getBackgroundColor());
×
662
  else
663
    w->setColor (wc_dialog.fg, wc_dialog.bg);
×
664
}
×
665

666
//----------------------------------------------------------------------
667
inline void drawVerticalFlatBorder (FWidget* w)
×
668
{
669
  const std::size_t width = w->getWidth();
×
670
  const std::size_t height = w->getHeight();
×
671

672
  for (std::size_t y{0}; y < height; y++)
×
673
  {
674
    w->print() << FPoint {0, int(y) + 1};
×
675

676
    if ( w->double_flatline_mask.left[uLong(y)] )
×
677
      // left+right line (on left side)
678
      w->print (UniChar::NF_rev_border_line_right_and_left);
×
679
    else
680
      // right line (on left side)
681
      w->print (UniChar::NF_rev_border_line_right);
×
682

683
    w->print() << FPoint {int(width) + 1, int(y) + 1};
×
684

685
    if ( w->double_flatline_mask.right[y] )
×
686
      // left+right line (on right side)
687
      w->print (UniChar::NF_rev_border_line_right_and_left);
×
688
    else
689
      // left line (on right side)
690
      w->print (UniChar::NF_border_line_left);
×
691
  }
692
}
×
693

694
//----------------------------------------------------------------------
695
inline void drawHorizontalFlatBorder (FWidget* w)
×
696
{
697
  const std::size_t width = w->getWidth();
×
698
  const std::size_t height = w->getHeight();
×
699
  w->print() << FPoint {1, 0};
×
700

701
  for (std::size_t x{0}; x < width; x++)
×
702
  {
703
    if ( w->double_flatline_mask.top[x] )
×
704
      // top+bottom line (at top)
705
      w->print (UniChar::NF_border_line_up_and_down);
×
706
    else
707
      // bottom line (at top)
708
      w->print (UniChar::NF_border_line_bottom);
×
709
  }
710

711
  w->print() << FPoint {1, int(height) + 1};
×
712

713
  for (std::size_t x{0}; x < width; x++)
×
714
  {
715
    if ( w->double_flatline_mask.bottom[x] )
×
716
      // top+bottom line (at bottom)
717
      w->print (UniChar::NF_border_line_up_and_down);
×
718
    else
719
      // top line (at bottom)
720
      w->print (UniChar::NF_border_line_upper);
×
721
  }
722
}
×
723

724
//----------------------------------------------------------------------
725
void drawFlatBorder (FWidget* w)
×
726
{
727
  if ( ! FVTerm::getFOutput()->isNewFont() )
×
728
    return;
×
729

730
  setWidgetColors(w);
×
731
  drawVerticalFlatBorder(w);
×
732
  drawHorizontalFlatBorder(w);
×
733
}
734

735
//----------------------------------------------------------------------
736
inline void clearVerticalFlatBorder (FWidget* w)
×
737
{
738
  const std::size_t width = w->getWidth();
×
739
  const std::size_t height = w->getHeight();
×
740

741
  for (std::size_t y{0}; y < height; y++)
×
742
  {
743
    // clear on left side
744
    w->print() << FPoint {0, int(y) + 1};
×
745

746
    if ( w->double_flatline_mask.left[y] )
×
747
      w->print (UniChar::NF_border_line_left);
×
748
    else
749
      w->print (' ');
×
750

751
    // clear on right side
752
    w->print() << FPoint {int(width) + 1, int(y) + 1};
×
753

754
    if ( w->double_flatline_mask.right[y] )
×
755
      w->print (UniChar::NF_rev_border_line_right);
×
756
    else
757
      w->print (' ');
×
758
  }
759
}
×
760

761
//----------------------------------------------------------------------
762
inline void clearHorizontalFlatBorder (FWidget* w)
×
763
{
764
  const std::size_t width = w->getWidth();
×
765
  const std::size_t height = w->getHeight();
×
766

767
  // clear at top
768
  w->print() << FPoint {1, 0};
×
769

770
  for (std::size_t x{0}; x < width; x++)
×
771
  {
772
    if ( w->double_flatline_mask.top[x] )
×
773
      w->print (UniChar::NF_border_line_upper);
×
774
    else
775
      w->print (' ');
×
776
  }
777

778
  // clear at bottom
779
  w->print() << FPoint {1, int(height) + 1};
×
780

781
  for (std::size_t x{0}; x < width; x++)
×
782
  {
783
    if ( w->double_flatline_mask.bottom[x] )
×
784
      w->print (UniChar::NF_border_line_bottom);
×
785
    else
786
      w->print (' ');
×
787
  }
788
}
×
789

790
//----------------------------------------------------------------------
791
void clearFlatBorder (FWidget* w)
×
792
{
793
  if ( ! FVTerm::getFOutput()->isNewFont() )
×
794
    return;
×
795

796
  setWidgetColors(w);
×
797
  clearVerticalFlatBorder(w);
×
798
  clearHorizontalFlatBorder(w);
×
799
}
800

801
//----------------------------------------------------------------------
802
inline void checkBorder (const FWidget* w, FRect& r)
×
803
{
804
  if ( r.x1_ref() > r.x2_ref() )
×
805
    std::swap (r.x1_ref(), r.x2_ref());
×
806

807
  if ( r.y1_ref() > r.y2_ref() )
×
808
    std::swap (r.y1_ref(), r.y2_ref());
×
809

810
  r.x1_ref() = std::max(r.x1_ref(), 1);
×
811
  r.y1_ref() = std::max(r.y1_ref(), 1);
×
812
  r.x2_ref() = std::min(r.x2_ref(), r.x1_ref() + int(w->getWidth()) - 1);
×
813
  r.y2_ref() = std::min(r.y2_ref(), r.y1_ref() + int(w->getHeight()) - 1);
×
814
}
×
815

816
//----------------------------------------------------------------------
817
void drawBorder (FWidget* w, const FRect& r)
×
818
{
819
  FRect rect = r;
×
820
  checkBorder (w, rect);
×
821

822
  if ( r.getWidth() < 3 )
×
823
    return;
×
824

825
  if ( FVTerm::getFOutput()->isNewFont() )
×
826
    drawNewFontBox (w, rect);
×
827
  else
828
    drawBox (w, rect);
×
829
}
830

831
//----------------------------------------------------------------------
832
void drawListBorder (FWidget* w, const FRect& r)
×
833
{
834
  FRect rect = r;
×
835
  checkBorder (w, rect);
×
836

837
  if ( r.getWidth() < 3 )
×
838
    return;
×
839

840
  if ( FVTerm::getFOutput()->isNewFont() )
×
841
    drawNewFontListBox (w, rect);
×
842
  else
843
    drawBox (w, rect);
×
844
}
845

846
//----------------------------------------------------------------------
847
inline void drawBox (FWidget* w, const FRect& r)
×
848
{
849
  // Use box-drawing characters to draw a border
850

851
  static constexpr std::array<wchar_t, 8> box_char
852
  {{
853
    static_cast<wchar_t>(UniChar::BoxDrawingsDownAndRight),  // ┌
854
    static_cast<wchar_t>(UniChar::BoxDrawingsHorizontal),    // ─
855
    static_cast<wchar_t>(UniChar::BoxDrawingsDownAndLeft),   // ┐
856
    static_cast<wchar_t>(UniChar::BoxDrawingsVertical),      // │
857
    static_cast<wchar_t>(UniChar::BoxDrawingsVertical),      // │
858
    static_cast<wchar_t>(UniChar::BoxDrawingsUpAndRight),    // └
859
    static_cast<wchar_t>(UniChar::BoxDrawingsHorizontal),    // ─
860
    static_cast<wchar_t>(UniChar::BoxDrawingsUpAndLeft)      // ┘
861
  }};
862

863
  drawGenericBox (w, r, box_char);
×
864
}
×
865

866
//----------------------------------------------------------------------
867
inline void drawNewFontBox (FWidget* w, const FRect& r)
×
868
{
869
  // Use new graphical font characters to draw a border
870

871
  static constexpr std::array<wchar_t, 8> box_char
872
  {{
873
    static_cast<wchar_t>(UniChar::NF_border_corner_middle_upper_left),   // ┌
874
    static_cast<wchar_t>(UniChar::NF_border_line_horizontal),            // ─
875
    static_cast<wchar_t>(UniChar::NF_border_corner_middle_upper_right),  // ┐
876
    static_cast<wchar_t>(UniChar::NF_border_line_vertical),              // │
877
    static_cast<wchar_t>(UniChar::NF_border_line_vertical),              // │
878
    static_cast<wchar_t>(UniChar::NF_border_corner_middle_lower_left),   // └
879
    static_cast<wchar_t>(UniChar::NF_border_line_horizontal),            // ─
880
    static_cast<wchar_t>(UniChar::NF_border_corner_middle_lower_right)   // ┘
881
  }};
882

883
  drawGenericBox (w, r, box_char);
×
884
}
×
885

886
//----------------------------------------------------------------------
887
void drawNewFontUShapedBox (FWidget* w, const FRect& r)
×
888
{
889
  // Draw a new graphical font U-shaped frame
890

891
  static constexpr std::array<wchar_t, 8> box_char
892
  {{
893
    static_cast<wchar_t>(UniChar::NF_border_line_left),              // border left ⎸
894
    L' ',                                                            // space ' '
895
    static_cast<wchar_t>(UniChar::NF_rev_border_line_right),         // border right⎹
896
    static_cast<wchar_t>(UniChar::NF_border_line_left),              // border left ⎸
897
    static_cast<wchar_t>(UniChar::NF_rev_border_line_right),         // border right⎹
898
    static_cast<wchar_t>(UniChar::NF_border_corner_lower_left),      // ⎣
899
    static_cast<wchar_t>(UniChar::NF_border_line_bottom),            // _
900
    static_cast<wchar_t>(UniChar::NF_rev_border_corner_lower_right)  // ⎦
901
  }};
902

903
  drawGenericBox (w, r, box_char);
×
904
}
×
905

906
//----------------------------------------------------------------------
907
inline void drawNewFontListBox (FWidget* w, const FRect& r)
×
908
{
909
  static constexpr std::array<wchar_t, 8> box_char
910
  {{
911
    static_cast<wchar_t>(UniChar::NF_border_line_middle_left_down),  // ┌
912
    static_cast<wchar_t>(UniChar::NF_border_line_horizontal),        // ─
913
    static_cast<wchar_t>(UniChar::NF_border_line_left_down),         // ╷
914
    static_cast<wchar_t>(UniChar::NF_border_line_left),              // border left ⎸
915
    static_cast<wchar_t>(UniChar::NF_border_line_left),              // border left ⎸
916
    static_cast<wchar_t>(UniChar::NF_border_line_middle_right_up),   // └
917
    static_cast<wchar_t>(UniChar::NF_border_line_horizontal),        // ─
918
    static_cast<wchar_t>(UniChar::NF_border_line_left_up)            // ╵
919
  }};
920

921
  drawGenericBox (w, r, box_char);
×
922
}
×
923

924
//----------------------------------------------------------------------
925
struct GenericBoxData
926
{
927
  // Constructor
928
  GenericBoxData ( FWidget* w, const FRect& r
×
929
                 , const std::array<wchar_t, 8>& chars )
930
    : box_char{chars}
×
931
    , area{*w->getPrintArea()}
×
932
    , x_offset{w->woffset.getX1() + w->getX() - area.position.x - 1}
×
933
    , y_offset{w->woffset.getY1() + w->getY() - area.position.y - 1}
×
934
    , x1{int(x_offset) + r.getX1() - 1}
×
935
    , y1{int(y_offset) + r.getY1() - 1}
×
936
    , x2{int(x_offset) + r.getX2() - 1}
×
937
    , y2{int(y_offset) + r.getY2() - 1}
×
938
    , max_width{uInt(area.size.width + area.shadow.width - 1)}
×
939
    , width{uInt(r.getWidth())}
×
940
    , line_length{width - 2}
×
941
    , is_transparent{(fchar.attr.byte[1] & internal::var::print_trans_mask) != 0}
×
942
    , trans_count_increment{uInt(is_transparent) * width}
×
943
  {
944
    // Prepare the first character to draw the box
945
    fchar.attr.bit.char_width = 1;
×
946
    fchar.ch[1] = L'\0';
×
947
  }
×
948

949
  inline void updateChanges (uInt y, uInt xmin, uInt xmax)
×
950
  {
951
    auto& changes = area.changes_in_line[y];
×
952
    changes.xmin = std::min(changes.xmin, xmin);
×
953
    changes.xmax = std::min(max_width, std::max(changes.xmax, xmax));
×
954
    changes.trans_count += trans_count_increment;
×
955
  }
×
956

957
  // Data members
958
  const std::array<wchar_t, 8>& box_char{};
959
  FChar& fchar{FVTermAttribute::getAttribute()};
960
  FVTerm::FTermArea& area;
961
  const int  x_offset{0};
962
  const int  y_offset{0};
963
  const int  x1{0};
964
  const int  y1{0};
965
  const int  x2{-1};
966
  const int  y2{-1};
967
  const uInt max_width{0};
968
  const uInt width{0};
969
  const uInt line_length{0};
970
  const bool is_transparent{false};
971
  const uInt trans_count_increment{0};
972
};
973

974
//----------------------------------------------------------------------
975
inline void drawBoxTopLine (GenericBoxData& bd)
×
976
{
977
  // Draw the horizontal top line of the box
978

979
  auto area_iter = bd.area.getFCharIterator(bd.x1, bd.y1);
×
980

981
  bd.fchar.ch[0] = bd.box_char[0];  // Upper left corner
×
982
  *area_iter = bd.fchar;
×
983

984
  bd.fchar.ch[0] = bd.box_char[1];  // Upper line
×
985
  area_iter = std::fill_n(area_iter + 1, bd.line_length, bd.fchar);
×
986

987
  bd.fchar.ch[0] = bd.box_char[2];  // Upper right corner
×
988
  *area_iter = bd.fchar;
×
989

990
  // Update area_changes for the top line
991
  bd.updateChanges(uInt(bd.y1), uInt(bd.x1), uInt(bd.x2));
×
992
}
×
993

994
//----------------------------------------------------------------------
995
inline void drawBoxSides (GenericBoxData& bd)
×
996
{
997
  // Draw the vertical sides of the box
998

999
  for (auto y = bd.y1 + 1; y < bd.y2; y++)
×
1000
  {
1001
    auto left_iter = bd.area.getFCharIterator(bd.x1, y);
×
1002
    auto right_iter = left_iter + bd.width - 1;
×
1003

1004
    bd.fchar.ch[0] = bd.box_char[3];  // Left line
×
1005
    *left_iter = bd.fchar;
×
1006

1007
    bd.fchar.ch[0] = bd.box_char[4];  // Right line
×
1008
    *right_iter = bd.fchar;
×
1009

1010
    // Update area_changes for the sides
1011
    bd.updateChanges(uInt(y), uInt(bd.x1), uInt(bd.x2));
×
1012
  }
1013
}
×
1014

1015
//----------------------------------------------------------------------
1016
inline void drawBoxBottomLine (GenericBoxData& bd)
×
1017
{
1018
  // Draw the horizontal bottom line of the box
1019

1020
  auto area_iter = bd.area.getFCharIterator(bd.x1, bd.y2);
×
1021

1022
  bd.fchar.ch[0] = bd.box_char[5];  // Upper left corner
×
1023
  *area_iter = bd.fchar;
×
1024

1025
  bd.fchar.ch[0] = bd.box_char[6];  // Bottom line
×
1026
  area_iter = std::fill_n(area_iter + 1, bd.line_length, bd.fchar);
×
1027

1028
  bd.fchar.ch[0] = bd.box_char[7];  // Bottom left corner
×
1029
  *area_iter = bd.fchar;
×
1030

1031
  // Update area_changes for the bottom line
1032
  bd.updateChanges(uInt(bd.y2), uInt(bd.x1), uInt(bd.x2));
×
1033
}
×
1034

1035
//----------------------------------------------------------------------
1036
void drawGenericBox ( FWidget* w, const FRect& r
×
1037
                    , const std::array<wchar_t, 8>& box_char )
1038
{
1039
  if ( ! w || ! w->getPrintArea() || r.getWidth() < 3 )
×
1040
    return;
×
1041

1042
  GenericBoxData box_data(w, r, box_char);
×
1043

1044
  drawBoxTopLine (box_data);
×
1045
  drawBoxSides (box_data);
×
1046
  drawBoxBottomLine (box_data);
×
1047
  box_data.area.has_changes = true;  // Mark area as having changes
×
1048
  // Update row changes
1049
  auto& changes_in_row = box_data.area.changes_in_row;
×
1050
  changes_in_row.ymin = std::min(changes_in_row.ymin, uInt(box_data.y1));
×
1051
  changes_in_row.ymax = std::max(changes_in_row.ymax, uInt(box_data.y2));
×
1052
}
1053

1054
//----------------------------------------------------------------------
1055
void updateStatusbar (const FWidget* w, bool need_focus)
×
1056
{
1057
  if ( w->hasFocus() != need_focus )
×
1058
    return;
×
1059

1060
  if ( auto sbar = w->getStatusBar() )
×
1061
  {
1062
    const auto& msg = w->getStatusbarMessage();
×
1063
    const auto& curMsg = sbar->getMessage();
×
1064

1065
    if ( curMsg != msg )
×
1066
    {
1067
      sbar->setMessage(msg);
×
1068
      sbar->drawMessage();
×
1069
    }
1070
  }
×
1071
}
1072

1073
//----------------------------------------------------------------------
1074
void drawStatusBarMessage()
×
1075
{
1076
  if ( FWidget::getStatusBar() )
×
1077
    FWidget::getStatusBar()->drawMessage();
×
1078
}
×
1079

1080
}  // 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