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

gansm / finalcut / #774

16 Feb 2026 10:59PM UTC coverage: 69.016% (+0.001%) from 69.015%
#774

push

travis-ci

gansm
FCellColor explicit constructor

1 of 8 new or added lines in 3 files covered. (12.5%)

4 existing lines in 1 file now uncovered.

37627 of 54519 relevant lines covered (69.02%)

250.7 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' },
NEW
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
{
432
  if ( d.shadow_width == 0 )
×
433
    return;
×
434

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

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

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

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

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

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

478
  for (std::size_t i{0}; i < s_height; i++)
×
479
  {
480
    const auto y = start_y + i;
×
481
    auto& changes = changes_in_line[y];
×
482
    changes.xmin = 0;
×
483
    changes.xmax = xmax;
×
484
    changes.trans_count += total_width;
×
485
    std::fill (ptr, std::next(ptr, s_width), d.transparent_char);
×
486
    ptr = std::next(ptr, s_width);
×
487
    std::fill (ptr, std::next(ptr, width), d.color_overlay_char);
×
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' },
NEW
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' },
NEW
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' },
NEW
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
    auto bg = p->getBackgroundColor();
×
540
    shadow_char[0].color.setBgColor(bg);
×
541
    shadow_char[1].color.setBgColor(bg);
×
542
    shadow_char[2].color.setBgColor(bg);
×
543
    shadow_char[3].color.setBgColor(bg);
×
544
  }
545

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

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

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

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

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

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

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

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

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

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

625
  GenericBlockShadowData bsd(w, shadow_char);
×
626

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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