diff --git a/far/FarCze.hlf.m4 b/far/FarCze.hlf.m4 index 3f7b142b19..b831245f87 100644 --- a/far/FarCze.hlf.m4 +++ b/far/FarCze.hlf.m4 @@ -4404,6 +4404,28 @@ slow down the directory reading. #Typ sloupce stavového řádku# a #Šířka sloupce stavového řádku# - podobně jako "Typ sloupců" a "Šířka sloupců", ale pro panel stavové řádky. + Multiple status lines are supported. To define them, separate column type +and width groups with the #|# (pipe) character. Each group describes one +status line. + + For example, #N,S|Z# in #Status line column types# and #0,10|0# in +#Status line column widths# will display file name and size on the first +status line, and file description on the second one. + + Empty status lines are also allowed. For example, #N,S||Z# defines three +status lines: file name and size on the first line, an empty second line, +and file description on the third one. + + Parsing of #Status line column widths# is relaxed. Widths are assigned +according to the layout specified in #Status line column types#. Therefore, +in addition to #|#, the #,# (comma) character can also be used between +width groups, and omitted empty groups are inferred automatically. + + For example, for #N,S|Z# it is allowed to specify #0,10,0# instead of +#0,10|0#. For #N,S||Z# it is also allowed to specify #0,10,0# instead of +#0,10||0#. In both cases the widths are distributed using the structure of +#Status line column types#. + #Celoobrazovkový režim# - nastaví celoobrazovkový pohled místo půlobrazového. #Zarovnat přípony souborů# - zobrazení přípon souborů zarovnaně. diff --git a/far/FarEng.hlf.m4 b/far/FarEng.hlf.m4 index f09359b3a1..321fa8ceee 100644 --- a/far/FarEng.hlf.m4 +++ b/far/FarEng.hlf.m4 @@ -4274,53 +4274,53 @@ files will be listed on a single stripe. The following column types are supported: - N[M[D],O,R[F],N] - file name, where: - M - ^show selection marks where: - D - dynamic selection marks; - O - ^show names without paths (intended mostly for ~plugins~@Plugins@); - R - ^right align names that do not fit in column, where: - F - right align all names; - N - ^do not show extensions in name column; + #N[M[D],O,R[F],N]# - file name, where: + #M# - ^show selection marks where: + #D# - dynamic selection marks; + #O# - ^show names without paths (intended mostly for ~plugins~@Plugins@); + #R# - ^right align names that do not fit in column, where: + #F# - right align all names; + #N# - ^do not show extensions in name column; These modifiers can be used in combination, for example NMR. - X[R] - file extension, where: - R - ^right align file extension; + #X[R]# - file extension, where: + #R# - ^right align file extension; - S[C,T,F,E] - file size - P[C,T,F,E] - allocation file size - G[C,T,F,E] - size of file streams, where: - C - ^group digits using the character from Windows settings; - T - ^use decimal units instead of binary, + #S[C,T,F,E]# - file size + #P[C,T,F,E]# - allocation file size + #G[C,T,F,E]# - size of file streams, where: + #C# - ^group digits using the character from Windows settings; + #T# - ^use decimal units instead of binary, i.e., to calculate kilobytes, the size will be divided by 1000 instead of by 1024; in this mode unit character is shown in lower case, e.g. #k#, #m#, #g# instead of #K#, #M#, #G#; - F - ^show size as a decimal fraction with + #F# - ^show size as a decimal fraction with no more than three digits before decimal point, e.g. 999 bytes will be shown as #999#, while 1024 bytes as #1.00 K#; note that the behavior depends on whether the #T# modifier is used; - E - ^economic mode, no space between the + #E# - ^economic mode, no space between the size and the unit character, e.g. #1.00k#; - D - file last write date; - T - file last write time; + #D# - file last write date; + #T# - file last write time; - DM[B,M] - file last write date and time; - DC[B,M] - file creation date and time; - DA[B,M] - file last access date and time; - DE[B,M] - file change date and time, where: - B - brief (Unix style) file time format; - M - use text month names; + #DM[B,M]# - file last write date and time; + #DC[B,M]# - file creation date and time; + #DA[B,M]# - file last access date and time; + #DE[B,M]# - file change date and time, where: + #B# - brief (Unix style) file time format; + #M# - use text month names; - A - file attributes; - Z - file descriptions; + #A# - file attributes; + #Z# - file descriptions; - O[L] - file owner, where: - L - show domain name; + #O[L]# - file owner, where: + #L# - show domain name; - LN - number of hard links; + #LN# - number of hard links; - F - number of streams. + #F# - number of streams. If the column types description contains more than one file name column, the file panel will be displayed in multicolumn form. @@ -4369,7 +4369,29 @@ to the display of seconds and milliseconds. slow down the directory reading. #Status line column types# and #Status line column widths# - -similar to "Column types" and "Column widths", but for panel status line. +similar to #Column types# and #Column widths#, but for panel status line. + + Multiple status lines are supported. To define them, separate column type +and width groups with the #|# (pipe) character. Each group describes one +status line. + + For example, #N,S|Z# in #Status line column types# and #0,10|0# in +#Status line column widths# will display file name and size on the first +status line, and file description on the second one. + + Empty status lines are also allowed. For example, #N,S||Z# defines three +status lines: file name and size on the first line, an empty second line, +and file description on the third one. + + Parsing of #Status line column widths# is relaxed. Widths are assigned +according to the layout specified in #Status line column types#. Therefore, +in addition to #|#, the #,# (comma) character can also be used between +width groups, and omitted empty groups are inferred automatically. + + For example, for #N,S|Z# it is allowed to specify #0,10,0# instead of +#0,10|0#. For #N,S||Z# it is also allowed to specify #0,10,0# instead of +#0,10||0#. In both cases the widths are distributed using the structure of +#Status line column types#. #Fullscreen view# - force fullscreen view instead of half-screen. diff --git a/far/FarGer.hlf.m4 b/far/FarGer.hlf.m4 index 512266977e..408f00c495 100644 --- a/far/FarGer.hlf.m4 +++ b/far/FarGer.hlf.m4 @@ -4452,6 +4452,28 @@ slow down the directory reading. wie "Spaltentypen" und "Spaltenbreite", jedoch für die Statuszeile des Fensters. + Multiple status lines are supported. To define them, separate column type +and width groups with the #|# (pipe) character. Each group describes one +status line. + + For example, #N,S|Z# in #Status line column types# and #0,10|0# in +#Status line column widths# will display file name and size on the first +status line, and file description on the second one. + + Empty status lines are also allowed. For example, #N,S||Z# defines three +status lines: file name and size on the first line, an empty second line, +and file description on the third one. + + Parsing of #Status line column widths# is relaxed. Widths are assigned +according to the layout specified in #Status line column types#. Therefore, +in addition to #|#, the #,# (comma) character can also be used between +width groups, and omitted empty groups are inferred automatically. + + For example, for #N,S|Z# it is allowed to specify #0,10,0# instead of +#0,10|0#. For #N,S||Z# it is also allowed to specify #0,10,0# instead of +#0,10||0#. In both cases the widths are distributed using the structure of +#Status line column types#. + #Vollbildschirm# - erzwingt Anzeige des Fensters anstelle über der halben über die volle Bildschirmseite. diff --git a/far/FarHun.hlf.m4 b/far/FarHun.hlf.m4 index cb0d9800e7..17fae0a219 100644 --- a/far/FarHun.hlf.m4 +++ b/far/FarHun.hlf.m4 @@ -4472,6 +4472,28 @@ slow down the directory reading. beállítása hasonlóan működik az "Oszloptípusokhoz" és "Oszlopszélességekhez", de a panelek állapotsorára hat. + Multiple status lines are supported. To define them, separate column type +and width groups with the #|# (pipe) character. Each group describes one +status line. + + For example, #N,S|Z# in #Status line column types# and #0,10|0# in +#Status line column widths# will display file name and size on the first +status line, and file description on the second one. + + Empty status lines are also allowed. For example, #N,S||Z# defines three +status lines: file name and size on the first line, an empty second line, +and file description on the third one. + + Parsing of #Status line column widths# is relaxed. Widths are assigned +according to the layout specified in #Status line column types#. Therefore, +in addition to #|#, the #,# (comma) character can also be used between +width groups, and omitted empty groups are inferred automatically. + + For example, for #N,S|Z# it is allowed to specify #0,10,0# instead of +#0,10|0#. For #N,S||Z# it is also allowed to specify #0,10,0# instead of +#0,10||0#. In both cases the widths are distributed using the structure of +#Status line column types#. + #Teljes képernyős nézet# - osztott képernyő helyett a fájlpanel elfoglalja a teljes képernyőszélességet. diff --git a/far/FarPol.hlf.m4 b/far/FarPol.hlf.m4 index d7ea23d314..146b11f658 100644 --- a/far/FarPol.hlf.m4 +++ b/far/FarPol.hlf.m4 @@ -4374,6 +4374,28 @@ może znacząco spowolnić odczyt folderu. #Typy kolumn linii statusu# i #Szerokości kolumn linii statusu# - podobnie do "Typów kolumn" i "Szerokości kolumn", ale dotyczą linii statusu panelu. + Multiple status lines are supported. To define them, separate column type +and width groups with the #|# (pipe) character. Each group describes one +status line. + + For example, #N,S|Z# in #Status line column types# and #0,10|0# in +#Status line column widths# will display file name and size on the first +status line, and file description on the second one. + + Empty status lines are also allowed. For example, #N,S||Z# defines three +status lines: file name and size on the first line, an empty second line, +and file description on the third one. + + Parsing of #Status line column widths# is relaxed. Widths are assigned +according to the layout specified in #Status line column types#. Therefore, +in addition to #|#, the #,# (comma) character can also be used between +width groups, and omitted empty groups are inferred automatically. + + For example, for #N,S|Z# it is allowed to specify #0,10,0# instead of +#0,10|0#. For #N,S||Z# it is also allowed to specify #0,10,0# instead of +#0,10||0#. In both cases the widths are distributed using the structure of +#Status line column types#. + #Widok pełnoekranowy# - wymusza wyświetlanie na pełnym ekranie zamiast na połowie. #Wyrównaj rozszerzenia plików# - pokazuje wyrównane rozszerzenia plików. diff --git a/far/FarRus.hlf.m4 b/far/FarRus.hlf.m4 index eff3cc932c..6baa4a2cbf 100644 --- a/far/FarRus.hlf.m4 +++ b/far/FarRus.hlf.m4 @@ -4429,6 +4429,28 @@ $ #Настройка режимов просмотра панели файло аналогично "Типам колонок" и "Ширине колонок", но для строки статуса панели. + Multiple status lines are supported. To define them, separate column type +and width groups with the #|# (pipe) character. Each group describes one +status line. + + For example, #N,S|Z# in #Status line column types# and #0,10|0# in +#Status line column widths# will display file name and size on the first +status line, and file description on the second one. + + Empty status lines are also allowed. For example, #N,S||Z# defines three +status lines: file name and size on the first line, an empty second line, +and file description on the third one. + + Parsing of #Status line column widths# is relaxed. Widths are assigned +according to the layout specified in #Status line column types#. Therefore, +in addition to #|#, the #,# (comma) character can also be used between +width groups, and omitted empty groups are inferred automatically. + + For example, for #N,S|Z# it is allowed to specify #0,10,0# instead of +#0,10|0#. For #N,S||Z# it is also allowed to specify #0,10,0# instead of +#0,10||0#. In both cases the widths are distributed using the structure of +#Status line column types#. + #Полноэкранный режим# - показывать панель во весь экран вместо половины экрана. diff --git a/far/FarSky.hlf.m4 b/far/FarSky.hlf.m4 index 75eb6ab538..8d8fc7ad1e 100644 --- a/far/FarSky.hlf.m4 +++ b/far/FarSky.hlf.m4 @@ -4364,7 +4364,29 @@ sekundy resp. milisekundy. slow down the directory reading. #Typy stavového riadku# a #Šírky stavového riadku# - -sú podobné "Typom stĺpcov" a "Šírkam stĺpcov", ale platia pre stavový riadok. +sú podobné #Typom stĺpcov# a #Šírkam stĺpcov#, ale platia pre stavový riadok. + + Multiple status lines are supported. To define them, separate column type +and width groups with the #|# (pipe) character. Each group describes one +status line. + + For example, #N,S|Z# in #Status line column types# and #0,10|0# in +#Status line column widths# will display file name and size on the first +status line, and file description on the second one. + + Empty status lines are also allowed. For example, #N,S||Z# defines three +status lines: file name and size on the first line, an empty second line, +and file description on the third one. + + Parsing of #Status line column widths# is relaxed. Widths are assigned +according to the layout specified in #Status line column types#. Therefore, +in addition to #|#, the #,# (comma) character can also be used between +width groups, and omitted empty groups are inferred automatically. + + For example, for #N,S|Z# it is allowed to specify #0,10,0# instead of +#0,10|0#. For #N,S||Z# it is also allowed to specify #0,10,0# instead of +#0,10||0#. In both cases the widths are distributed using the structure of +#Status line column types#. #Celá obrazovka# - nastaví jeden panel na celú obrazovku namiesto zvyčajnej polovičky. diff --git a/far/FarUkr.hlf.m4 b/far/FarUkr.hlf.m4 index d1052dbeaf..4f9a265729 100644 --- a/far/FarUkr.hlf.m4 +++ b/far/FarUkr.hlf.m4 @@ -4436,6 +4436,28 @@ slow down the directory reading. аналогічно "Типам стовпчиків" і "Ширині стовпчиків", але для рядка статусу панелі. + Multiple status lines are supported. To define them, separate column type +and width groups with the #|# (pipe) character. Each group describes one +status line. + + For example, #N,S|Z# in #Status line column types# and #0,10|0# in +#Status line column widths# will display file name and size on the first +status line, and file description on the second one. + + Empty status lines are also allowed. For example, #N,S||Z# defines three +status lines: file name and size on the first line, an empty second line, +and file description on the third one. + + Parsing of #Status line column widths# is relaxed. Widths are assigned +according to the layout specified in #Status line column types#. Therefore, +in addition to #|#, the #,# (comma) character can also be used between +width groups, and omitted empty groups are inferred automatically. + + For example, for #N,S|Z# it is allowed to specify #0,10,0# instead of +#0,10|0#. For #N,S||Z# it is also allowed to specify #0,10,0# instead of +#0,10||0#. In both cases the widths are distributed using the structure of +#Status line column types#. + #Повноекранний режим# - показувати панель на весь екран замість половини екрана. diff --git a/far/config.cpp b/far/config.cpp index 949cc38824..b9db5bf0d9 100644 --- a/far/config.cpp +++ b/far/config.cpp @@ -953,7 +953,7 @@ static void ResetViewModes(std::span const Modes, int const I { static const struct { - std::initializer_list PanelColumns, StatusColumns; + std::initializer_list PanelColumns, StatusLine; unsigned long long Flags; } InitialModes[] @@ -1113,7 +1113,7 @@ static void ResetViewModes(std::span const Modes, int const I const auto InitMode = [](const auto& src, auto& dst) { dst.PanelColumns = src.PanelColumns; - dst.StatusColumns = src.StatusColumns; + dst.StatusLines = { src.StatusLine }; dst.Flags = src.Flags; dst.Name.clear(); }; @@ -1348,7 +1348,7 @@ void Options::SetFilePanelModes() ModeDlg[MD_EDITNAME].strData = CurrentSettings.Name; std::tie(ModeDlg[MD_EDITTYPES].strData, ModeDlg[MD_EDITWIDTHS].strData) = SerialiseViewSettings(CurrentSettings.PanelColumns); - std::tie(ModeDlg[MD_EDITSTATUSTYPES].strData, ModeDlg[MD_EDITSTATUSWIDTHS].strData) = SerialiseViewSettings(CurrentSettings.StatusColumns); + std::tie(ModeDlg[MD_EDITSTATUSTYPES].strData, ModeDlg[MD_EDITSTATUSWIDTHS].strData) = serialize_status_line_settings(CurrentSettings.StatusLines); } int ExitCode; @@ -1376,7 +1376,7 @@ void Options::SetFilePanelModes() NewSettings.Name = ModeDlg[MD_EDITNAME].strData; NewSettings.PanelColumns = DeserialiseViewSettings(ModeDlg[MD_EDITTYPES].strData, ModeDlg[MD_EDITWIDTHS].strData); - NewSettings.StatusColumns = DeserialiseViewSettings(ModeDlg[MD_EDITSTATUSTYPES].strData, ModeDlg[MD_EDITSTATUSWIDTHS].strData); + NewSettings.StatusLines = deserialize_status_line_settings(ModeDlg[MD_EDITSTATUSTYPES].strData, ModeDlg[MD_EDITSTATUSWIDTHS].strData); } else { @@ -2861,7 +2861,7 @@ void Options::ReadPanelModes() if (!StatusColumnTitles.empty()) { const auto StatusColumnWidths = cfg->GetValue(Key, ModesStatusColumnWidthsName); - i.StatusColumns = DeserialiseViewSettings(StatusColumnTitles, StatusColumnWidths); + i.StatusLines = deserialize_status_line_settings(StatusColumnTitles, StatusColumnWidths); } }; @@ -2897,7 +2897,7 @@ void Options::SavePanelModes(bool always) const auto SaveMode = [&](HierarchicalConfig::key const ModesKey, PanelViewSettings const& Item, size_t Index) { const auto [PanelTitles, PanelWidths] = SerialiseViewSettings(Item.PanelColumns); - const auto [StatusTitles, StatusWidths] = SerialiseViewSettings(Item.StatusColumns); + const auto [StatusTitles, StatusWidths] = serialize_status_line_settings(Item.StatusLines); const auto Key = cfg->CreateKey(ModesKey, str(Index)); diff --git a/far/filelist.cpp b/far/filelist.cpp index 7da0b3f2aa..9d7718e111 100644 --- a/far/filelist.cpp +++ b/far/filelist.cpp @@ -509,6 +509,37 @@ class FileList::background_updater FileList* m_Owner; }; +int FileList::visible_status_line_count() const +{ + if (!Global->Opt->ShowPanelStatus) + return 0; + + const auto TotalStatusLineCount = static_cast(m_ViewSettings.StatusLines.size()); + if (!TotalStatusLineCount) + return 0; + + const auto MaxVisibleStatusLineCount = m_Where.height() - 5; + + return std::min(TotalStatusLineCount, std::max(1, MaxVisibleStatusLineCount)); +} + +int FileList::status_footer_height() const +{ + const auto VisibleStatusLineCount = visible_status_line_count(); + + return VisibleStatusLineCount ? 1 + VisibleStatusLineCount : 0; +} + +int FileList::status_separator_y() const +{ + return m_Where.bottom - status_footer_height(); +} + +int FileList::status_text_top_y() const +{ + return m_Where.bottom - visible_status_line_count(); +} + file_panel_ptr FileList::create(window_ptr Owner) { return std::make_shared(private_tag(), Owner); @@ -3265,7 +3296,7 @@ bool FileList::ProcessMouse(const MOUSE_EVENT_RECORD *MouseEvent) if ( MouseEvent->dwMousePosition.Y > m_Where.top + Global->Opt->ShowColumnTitles && - MouseEvent->dwMousePosition.Y < m_Where.bottom - 2 * Global->Opt->ShowPanelStatus + MouseEvent->dwMousePosition.Y < m_Where.bottom - status_footer_height() ) { Parent()->SetActivePanel(shared_from_this()); @@ -3375,7 +3406,7 @@ bool FileList::ProcessMouse(const MOUSE_EVENT_RECORD *MouseEvent) return true; } - if (MouseEvent->dwMousePosition.Y >= m_Where.bottom - 2) + if (MouseEvent->dwMousePosition.Y >= m_Where.bottom - status_footer_height()) { Parent()->SetActivePanel(shared_from_this()); @@ -3387,7 +3418,7 @@ bool FileList::ProcessMouse(const MOUSE_EVENT_RECORD *MouseEvent) while_mouse_button_pressed([&](DWORD) { - if (IntKeyState.MousePos.y < m_Where.bottom - 2) + if (IntKeyState.MousePos.y < m_Where.bottom - status_footer_height()) return false; MoveCursorAndShow(1); @@ -7065,9 +7096,20 @@ void FileList::ReadFileNames(bool const KeepSelection, bool const UpdateEvenIfPa unordered_string_set ColumnsSet; - for (const auto& ColumnsContainer: { &m_ViewSettings.PanelColumns, &m_ViewSettings.StatusColumns }) + for (const auto& Column: m_ViewSettings.PanelColumns) { - for (const auto& Column: *ColumnsContainer) + if (Column.type == column_type::custom_0) + { + if (ColumnsSet.emplace(Column.title).second) + { + m_ContentNames.emplace_back(Column.title); + } + } + } + + for (const auto& StatusLine: m_ViewSettings.StatusLines) + { + for (const auto& Column: StatusLine) { if (Column.type == column_type::custom_0) { @@ -7793,7 +7835,7 @@ void FileList::FillParentPoint(FileListItem& Item) void FileList::UpdateHeight() { - m_Height = m_Where.height() - 2 - (Global->Opt->ShowColumnTitles? 1 : 0) - (Global->Opt->ShowPanelStatus? 2 : 0); + m_Height = m_Where.height() - 2 - (Global->Opt->ShowColumnTitles? 1 : 0) - status_footer_height(); } void FileList::DisplayObject() @@ -7905,9 +7947,6 @@ void FileList::ShowFileList(bool Fast) if (I == m_ViewSettings.PanelColumns.size() - 1) break; - if (m_ViewSettings.PanelColumns[I + 1].width < 0) - continue; - SetColor(COL_PANELBOX); ColumnPos += m_ViewSettings.PanelColumns[I].width; GotoXY(static_cast(ColumnPos), m_Where.top); @@ -8042,7 +8081,10 @@ void FileList::ShowFileList(bool Fast) if (m_ListData.empty()) { - SetScreen({ m_Where.left + 1, m_Where.bottom - 1, m_Where.right - 1, m_Where.bottom - 1 }, L' ', colors::PaletteColorToFarColor(COL_PANELTEXT)); + for (const auto Y: std::views::iota(status_text_top_y(), m_Where.bottom)) + { + SetScreen({ m_Where.left + 1, Y, m_Where.right - 1, Y }, L' ', colors::PaletteColorToFarColor(COL_PANELTEXT)); + } SetColor(COL_PANELTEXT); //??? //GotoXY(X1+1,Y2-1); //Text(string(X2 - X1 - 1, L' ')); @@ -8094,7 +8136,7 @@ void FileList::ShowFileList(bool Fast) ShowTotalSize(m_CachedOpenPanelInfo); } - ShowList(FALSE,0); + render_file_list(); ShowSelectedSize(); if (Global->Opt->ShowPanelScrollbar) @@ -8188,14 +8230,14 @@ void FileList::ShowSelectedSize() if (Global->Opt->ShowPanelStatus) { SetColor(COL_PANELBOX); - DrawSeparator(m_Where.bottom - 2); + DrawSeparator(status_separator_y()); for (size_t I = 0, ColumnPos = m_Where.left + 1; I < m_ViewSettings.PanelColumns.size() - 1; I++) { if (m_ViewSettings.PanelColumns[I].width < 0 || (I == m_ViewSettings.PanelColumns.size() - 2 && m_ViewSettings.PanelColumns[I+1].width < 0)) continue; ColumnPos += m_ViewSettings.PanelColumns[I].width; - GotoXY(static_cast(ColumnPos), m_Where.bottom - 2); + GotoXY(static_cast(ColumnPos), status_separator_y()); const auto DoubleLine = !((I + 1) % m_ColumnsInStripe); Text(BoxSymbols[DoubleLine?BS_B_H1V2:BS_B_H1V1]); @@ -8218,7 +8260,7 @@ void FileList::ShowSelectedSize() inplace::truncate_right(strSelStr, AvailableWidth); } SetColor(COL_PANELSELECTEDINFO); - GotoXY(static_cast(m_Where.left + BorderSize + (AvailableWidth - strSelStr.size()) / 2), m_Where.bottom - 2 * Global->Opt->ShowPanelStatus); + GotoXY(static_cast(m_Where.left + BorderSize + (AvailableWidth - strSelStr.size()) / 2), Global->Opt->ShowPanelStatus? status_separator_y() : m_Where.bottom); Text(L' '); Text(strSelStr); Text(L' '); @@ -8391,24 +8433,24 @@ void FileList::PrepareViewSettings(int ViewMode) if (m_CachedOpenPanelInfo.PanelModesArray[ViewMode].StatusColumnTypes && m_CachedOpenPanelInfo.PanelModesArray[ViewMode].StatusColumnWidths) { - m_ViewSettings.StatusColumns = DeserialiseViewSettings(m_CachedOpenPanelInfo.PanelModesArray[ViewMode].StatusColumnTypes, m_CachedOpenPanelInfo.PanelModesArray[ViewMode].StatusColumnWidths); + m_ViewSettings.StatusLines = deserialize_status_line_settings(m_CachedOpenPanelInfo.PanelModesArray[ViewMode].StatusColumnTypes, m_CachedOpenPanelInfo.PanelModesArray[ViewMode].StatusColumnWidths); } else if (m_CachedOpenPanelInfo.PanelModesArray[ViewMode].Flags&PMFLAGS_DETAILEDSTATUS) { - m_ViewSettings.StatusColumns = - { + m_ViewSettings.StatusLines = + {{ { column_type::name, COLFLAGS_RIGHTALIGN, 0, }, { column_type::size, 0, 8, }, { column_type::date, 0, 0, }, { column_type::time, 0, 5, }, - }; + }}; } else { - m_ViewSettings.StatusColumns = - { + m_ViewSettings.StatusLines = + {{ { column_type::name, COLFLAGS_RIGHTALIGN, 0, }, - }; + }}; } if (m_CachedOpenPanelInfo.PanelModesArray[ViewMode].Flags&PMFLAGS_FULLSCREEN) @@ -8450,7 +8492,10 @@ void FileList::PrepareViewSettings(int ViewMode) void FileList::PreparePanelView() { - PrepareColumnWidths(m_ViewSettings.StatusColumns, (m_ViewSettings.Flags&PVS_FULLSCREEN) != 0); + for (auto& StatusLine: m_ViewSettings.StatusLines) + { + PrepareColumnWidths(StatusLine, (m_ViewSettings.Flags&PVS_FULLSCREEN) != 0); + } PrepareColumnWidths(m_ViewSettings.PanelColumns, (m_ViewSettings.Flags&PVS_FULLSCREEN) != 0); PrepareStripes(m_ViewSettings.PanelColumns); } @@ -8458,6 +8503,9 @@ void FileList::PreparePanelView() void FileList::PrepareColumnWidths(std::vector& Columns, bool FullScreen) const { + if (Columns.empty()) + return; + int ZeroLengthCount = 0; int EmptyColumns = 0; int TotalPercentCount = 0; @@ -8634,443 +8682,494 @@ void FileList::HighlightBorder(int Level, int ListPos) const } } -void FileList::ShowList(int ShowStatus,int StartColumn) +void FileList::render_column(const FileListItem& Item, const column& Column, int ColumnWidth, + os::chrono::time_point const CurrentTime, int& MaxLeftPos, int& MinLeftPos) const { - const auto DataLock = lock_data(); - const auto& m_ListData = *DataLock; - - int StatusShown=FALSE; - int MaxLeftPos=0,MinLeftPos=FALSE; - size_t ColumnCount=ShowStatus ? m_ViewSettings.StatusColumns.size() : m_ViewSettings.PanelColumns.size(); - const auto& Columns = ShowStatus ? m_ViewSettings.StatusColumns : m_ViewSettings.PanelColumns; - - const auto CurrentTime = os::chrono::nt_clock::now(); - - for (int I = m_Where.top + 1 + Global->Opt->ShowColumnTitles, J = m_CurTopFile; I < m_Where.bottom - 2 * Global->Opt->ShowPanelStatus; I++, J++) + const auto calculate_left_pos = [&](const int Length, const bool RightAlign = false) { - int CurColumn=StartColumn; + if (Length <= ColumnWidth) + return 0; - if (ShowStatus) + int CurLeftPos = 0; + + if (LeftPos > 0) { - SetColor(COL_PANELTEXT); - GotoXY(m_Where.left + 1, m_Where.bottom - 1); + if (RightAlign) + { + CurLeftPos = Length - ColumnWidth; + } + else + { + CurLeftPos = std::min(LeftPos, Length - ColumnWidth); + MaxLeftPos = std::max(MaxLeftPos, CurLeftPos); + } + + return CurLeftPos; } - else + + if (RightAlign) { - GotoXY(m_Where.left + 1, I); + CurLeftPos = Length - ColumnWidth + LeftPos; + + if (CurLeftPos < 0) + { + CurLeftPos = 0; + MinLeftPos = std::min(MinLeftPos, ColumnWidth - Length); + } + else + MinLeftPos = std::min(MinLeftPos, LeftPos); } - int StatusLine=FALSE; - int Level = 1; + return CurLeftPos; + }; + + const auto substr_from_utf16_boundary = [](const string_view Str, int CurLeftPos) + { + if (CurLeftPos <= 0) + return Str; + + if (static_cast(CurLeftPos) >= Str.size()) + return string_view{}; + + if (is_valid_surrogate_pair(Str[CurLeftPos - 1], Str[CurLeftPos])) + ++CurLeftPos; + + return Str.substr(CurLeftPos); + }; + + const auto get_custom_column_data = [&] + { + const auto ColumnIndex = static_cast(Column.type) - static_cast(column_type::custom_0); + + if (ColumnIndex < Item.CustomColumns.size()) + if (const auto ColumnData = Item.CustomColumns[ColumnIndex]) + return ColumnData; + + const auto& ContentMapPtr = Item.ContentData(this); + if (!ContentMapPtr) + return L""; + + const auto Iterator = ContentMapPtr->find(Column.title); - for (const auto K: std::views::iota(0uz, ColumnCount)) + return Iterator != ContentMapPtr->cend() ? Iterator->second.c_str() : L""; + }; + + const auto ViewFlags = Column.type_flags; + + switch (const auto ColumnType = Column.type) + { + case column_type::name: { - int ListPos=J+CurColumn*m_Height; + if ((ViewFlags & COLFLAGS_MARK) && ColumnWidth > 2) + { + const auto SelectedMark = Item.Selected + ? L"√ "sv + : (ViewFlags & COLFLAGS_MARK_DYNAMIC ? L""sv : L" "sv); - if (ShowStatus) + Text(SelectedMark); + + ColumnWidth -= static_cast(SelectedMark.size()); + } + + if (Global->Opt->Highlight && Item.Colors && !Item.Colors->Mark.Mark.empty() && ColumnWidth > 1) { - if (m_CurFile!=ListPos) + const int MarkLength = static_cast(visual_string_length(Item.Colors->Mark.Mark)); + + if (ColumnWidth >= MarkLength) { - CurColumn++; - continue; + ColumnWidth -= MarkLength; + + Text(Item.Colors->Mark.Mark, MarkLength); } - else - StatusLine=TRUE; } - else + + string_view Name = Item.AlternateOrNormal(m_ShowShortNames); + + if (!(Item.Attributes & FILE_ATTRIBUTE_DIRECTORY) && (ViewFlags & COLFLAGS_NOEXTENSION) + || ViewFlags & COLFLAGS_NAMEONLY) { - SetShowColor(ListPos); + Name = name_ext(Name).first; } - int CurX=WhereX(); - int CurY=WhereY(); - int ShowDivider=TRUE; - const auto ColumnType = Columns[K].type; - int ColumnWidth=Columns[K].width; + const auto Length = static_cast(Name.size()); + const auto CurLeftPos = calculate_left_pos(Length, ViewFlags & COLFLAGS_RIGHTALIGN); - if (ColumnWidth<0) + const auto StrName = substr_from_utf16_boundary(Name, CurLeftPos); + + Text((ViewFlags & COLFLAGS_RIGHTALIGNFORCE ? fit_to_right : fit_to_left)(string(StrName), ColumnWidth)); + break; + } + + case column_type::size: + case column_type::size_compressed: + case column_type::streams_size: + { + const auto SizeToDisplay = + ColumnType == column_type::size_compressed + ? Item.AllocationSize(this) + : (ColumnType == column_type::streams_size ? Item.StreamsSize(this) : Item.FileSize); + + Text(FormatStr_Size(static_cast(SizeToDisplay), Item.FileName, Item.Attributes, + folder_size_display_type(Item.SizeState), Item.ReparseTag, ColumnType, ViewFlags, + ColumnWidth, m_CurDir)); + break; + } + + case column_type::date: + case column_type::time: + case column_type::date_write: + case column_type::date_creation: + case column_type::date_access: + case column_type::date_change: + { + const auto FileTime = [&]() -> os::chrono::time_point { - if (!ShowStatus && K==ColumnCount-1) + switch (ColumnType) { - SetColor(COL_PANELBOX); - GotoXY(CurX-1,CurY); - Text(CurX - 1 == m_Where.right? BoxSymbols[BS_V2] : L' '); + case column_type::date_creation: return Item.CreationTime; + case column_type::date_access: return Item.LastAccessTime; + case column_type::date_change: return Item.ChangeTime; + default: return Item.LastWriteTime; } + }(); - continue; - } + Text(FormatStr_DateTime(FileTime, ColumnType, ViewFlags, ColumnWidth, CurrentTime)); + break; + } + + case column_type::attributes: + Text(FormatStr_Attribute(Item.Attributes, ColumnWidth)); + break; + + case column_type::extension: + { + string_view ExtPtr; - if (ListPos < static_cast(m_ListData.size())) + if (!(Item.Attributes & FILE_ATTRIBUTE_DIRECTORY)) { - if (!ShowStatus && !StatusShown && m_CurFile==ListPos && Global->Opt->ShowPanelStatus) - { - ShowList(TRUE,CurColumn); - GotoXY(CurX,CurY); - StatusShown=TRUE; - SetShowColor(ListPos); - } + const auto& Name = Item.AlternateOrNormal(m_ShowShortNames); + ExtPtr = name_ext(Name).second; + } - if (!ShowStatus) - SetShowColor(ListPos); + if (!ExtPtr.empty()) + ExtPtr.remove_prefix(1); - if (ColumnType >= column_type::custom_0 && ColumnType <= column_type::custom_max) - { - size_t ColumnNumber = static_cast(ColumnType) - static_cast(column_type::custom_0); - const wchar_t *ColumnData = nullptr; + const auto Length = static_cast(ExtPtr.size()); + const auto CurLeftPos = calculate_left_pos(Length, ViewFlags & COLFLAGS_RIGHTALIGN); - if (ColumnNumber < m_ListData[ListPos].CustomColumns.size()) - ColumnData = m_ListData[ListPos].CustomColumns[ColumnNumber]; + const auto Ext = substr_from_utf16_boundary(ExtPtr, CurLeftPos); - if (!ColumnData) - { - const auto GetContentData = [&] - { - const auto& ContentMapPtr = m_ListData[ListPos].ContentData(this); - if (!ContentMapPtr) - return L""; - const auto Iterator = ContentMapPtr->find(Columns[K].title); - return Iterator != ContentMapPtr->cend()? Iterator->second.c_str() : L""; - }; - - ColumnData = GetContentData(); - } + Text((ViewFlags & COLFLAGS_RIGHTALIGN ? fit_to_right : fit_to_left)(string(Ext), ColumnWidth)); + break; + } - int CurLeftPos=0; + case column_type::description: + { + const auto Length = static_cast(std::wcslen(NullToEmpty(Item.DizText))); + const auto CurLeftPos = calculate_left_pos(Length); - if (!ShowStatus && LeftPos>0) - { - int Length = static_cast(std::wcslen(ColumnData)); - if (Length>ColumnWidth) - { - CurLeftPos = std::min(LeftPos, Length-ColumnWidth); - MaxLeftPos = std::max(MaxLeftPos, CurLeftPos); - } - } + auto DizText = Item.DizText ? substr_from_utf16_boundary(Item.DizText, CurLeftPos) : L""sv; - Text(fit_to_left(ColumnData + CurLeftPos, ColumnWidth)); - } - else - { - switch (ColumnType) - { - case column_type::name: - { - int Width=ColumnWidth; - const auto ViewFlags = Columns[K].type_flags; + if (const auto Pos = DizText.find(L'\4'); Pos != string::npos) + DizText.remove_suffix(DizText.size() - Pos); - if ((ViewFlags & COLFLAGS_MARK) && Width>2) - { - const auto Mark = m_ListData[ListPos].Selected? L"√ "sv : ViewFlags & COLFLAGS_MARK_DYNAMIC ? L""sv : L" "sv; - Text(Mark); - Width -= static_cast(Mark.size()); - } + Text(fit_to_left(string(DizText), ColumnWidth)); + break; + } - if (Global->Opt->Highlight && m_ListData[ListPos].Colors && !m_ListData[ListPos].Colors->Mark.Mark.empty() && Width>1) - { - const auto MarkLength = static_cast(visual_string_length(m_ListData[ListPos].Colors->Mark.Mark)); - if (Width >= MarkLength) - { - Width -= MarkLength; + case column_type::owner: + { + const auto& Owner = Item.Owner(this); + size_t Offset = 0; - const auto OldColor = GetColor(); - if (!ShowStatus) - SetShowColor(ListPos, false); + if (!(ViewFlags & COLFLAGS_FULLOWNER)) + { + const auto SlashPos = FindSlash(Owner); - Text(m_ListData[ListPos].Colors->Mark.Mark, MarkLength); - SetColor(OldColor); - } - } + if (SlashPos != string::npos) + Offset = SlashPos + 1; + } + else if (!Owner.empty() && path::is_separator(Owner.front())) + { + Offset = 1; + } - string_view Name = m_ListData[ListPos].AlternateOrNormal(m_ShowShortNames); + const auto Length = static_cast(Owner.size() - Offset); + const auto CurLeftPos = calculate_left_pos(Length); - if (!(m_ListData[ListPos].Attributes & FILE_ATTRIBUTE_DIRECTORY) && (ViewFlags & COLFLAGS_NOEXTENSION)) - { - Name = name_ext(Name).first; - } + Text(fit_to_left(string(substr_from_utf16_boundary(Owner, static_cast(Offset) + CurLeftPos)), + ColumnWidth)); + break; + } - const auto NameCopy = Name; + case column_type::links_number: + { + const auto Value = Item.NumberOfLinks(this); - if (ViewFlags & COLFLAGS_NAMEONLY) - { - //BUGBUG!!! - // !!! НЕ УВЕРЕН, но то, что отображается пустое - // пространство вместо названия - бага - Name = PointToFolderNameIfFolder(Name); - } + Text(fit_to_right(Value == FileListItem::values::unknown(Value) ? L"?"s : str(Value), ColumnWidth)); + break; + } - int CurLeftPos=0; - unsigned long long RightAlign=(ViewFlags & (COLFLAGS_RIGHTALIGN|COLFLAGS_RIGHTALIGNFORCE)); - int LeftBracket=FALSE,RightBracket=FALSE; + case column_type::streams_number: + { + const auto Value = Item.NumberOfStreams(this); - if (!ShowStatus && LeftPos) - { - if (const auto Length = static_cast(visual_string_length(Name)); Length > Width) - { - if (LeftPos>0) - { - if (!RightAlign) - { - CurLeftPos = std::min(LeftPos, Length-Width); + Text(fit_to_right(Value == FileListItem::values::unknown(Value) ? L"?"s : str(Value), ColumnWidth)); + break; + } - auto ActualLeftPos = 0; - for (; ActualLeftPos != CurLeftPos && static_cast(visual_string_length(Name)) > Width; ++ActualLeftPos) - { - encoding::utf16::remove_first_codepoint(Name); - } + default: + if (ColumnType >= column_type::custom_0 && ColumnType <= column_type::custom_max) + { + const auto ColumnData = get_custom_column_data(); - MaxLeftPos = std::max(MaxLeftPos, ActualLeftPos); - } - } - else if (RightAlign) - { - int CurRightPos=LeftPos; - - if (Length+CurRightPos(visual_string_length(Name)) > Width - CurRightPos) - { - encoding::utf16::remove_first_codepoint(Name); - } - - MinLeftPos = std::min(MinLeftPos, CurRightPos); - RightAlign=FALSE; - } - } - } + const auto Length = static_cast(std::wcslen(ColumnData)); + const auto CurLeftPos = calculate_left_pos(Length); - string strName; - const auto TooLong = ConvertName(Name, strName, Width, RightAlign,ShowStatus,m_ListData[ListPos].Attributes); + Text(fit_to_left(string(substr_from_utf16_boundary(ColumnData, CurLeftPos)), ColumnWidth)); + } - if (CurLeftPos) - LeftBracket=TRUE; + break; + } +} - if (TooLong) - { - (RightAlign? LeftBracket : RightBracket) = TRUE; - } +void FileList::render_status_lines(const FileListItem& Item, os::chrono::time_point const CurrentTime, + int& MaxLeftPos, int& MinLeftPos) const +{ + const auto& StatusLines = m_ViewSettings.StatusLines; + const int VisibleLineCount = visible_status_line_count(); - if (!ShowStatus) - { - if (m_ViewSettings.Flags&PVS_FILEUPPERTOLOWERCASE) - if (!(m_ListData[ListPos].Attributes & FILE_ATTRIBUTE_DIRECTORY) && !IsCaseMixed(NameCopy)) - inplace::lower(strName); + if (!VisibleLineCount || StatusLines.empty()) + return; - if ((m_ViewSettings.Flags&PVS_FOLDERUPPERCASE) && (m_ListData[ListPos].Attributes & FILE_ATTRIBUTE_DIRECTORY)) - inplace::upper(strName); + SetColor(COL_PANELTEXT); - if ((m_ViewSettings.Flags&PVS_FILELOWERCASE) && !(m_ListData[ListPos].Attributes & FILE_ATTRIBUTE_DIRECTORY)) - inplace::lower(strName); - } + const int StatusTopY = status_text_top_y(); - Text(strName, Width); + for (auto LineIndex = 0; LineIndex != VisibleLineCount; ++LineIndex) + { + const auto& Columns = StatusLines[LineIndex]; + const auto ColumnCount = Columns.size(); + int CurX = m_Where.left + 1; - if (!ShowStatus) - { - int NameX=WhereX(); + for (const auto ColumnIndex : std::views::iota(0uz, ColumnCount)) + { + const int ColumnWidth = Columns[ColumnIndex].width; - if (LeftBracket) - { - GotoXY(CurX-1,CurY); + if (ColumnWidth < 0) + continue; - if (Level == 1) - SetColor(COL_PANELBOX); + GotoXY(CurX, StatusTopY + LineIndex); - Text(openBracket); - SetShowColor(J); - } + render_column(Item, Columns[ColumnIndex], ColumnWidth, CurrentTime, MaxLeftPos, MinLeftPos); - if (RightBracket) - { - HighlightBorder(Level, ListPos); - GotoXY(NameX,CurY); - Text(closeBracket); - ShowDivider=FALSE; - - if (Level == m_ColumnsInStripe) - SetColor(COL_PANELTEXT); - else - SetShowColor(J); - } - } - } - break; - case column_type::extension: - { - string_view ExtPtr; - if (!(m_ListData[ListPos].Attributes & FILE_ATTRIBUTE_DIRECTORY)) - { - const auto& Name = m_ListData[ListPos].AlternateOrNormal(m_ShowShortNames); - ExtPtr = name_ext(Name).second; - } - if (!ExtPtr.empty()) - ExtPtr.remove_prefix(1); + CurX += ColumnWidth + 1; + } - const auto ViewFlags = Columns[K].type_flags; - Text((ViewFlags & COLFLAGS_RIGHTALIGN? fit_to_right : fit_to_left)(string(ExtPtr), ColumnWidth)); + if (CurX < m_Where.right) + { + Text(string(m_Where.right - CurX, L' ')); + } + } +} - if (!ShowStatus && static_cast(ExtPtr.size()) > ColumnWidth) - { - int NameX=WhereX(); +void FileList::render_file_list() +{ + const auto DataLock = lock_data(); + const auto& ListData = *DataLock; + const auto CurrentTime = os::chrono::nt_clock::now(); - HighlightBorder(Level, ListPos); + bool StatusShown = !Global->Opt->ShowPanelStatus; + int MaxLeftPos = 0, MinLeftPos = 0; - GotoXY(NameX,CurY); - Text(closeBracket); - ShowDivider=FALSE; + const auto ColumnCount = m_ViewSettings.PanelColumns.size(); + const auto& Columns = m_ViewSettings.PanelColumns; - if (Level == m_ColumnsInStripe) - SetColor(COL_PANELTEXT); - else - SetShowColor(J); - } + for (int CurY = m_Where.top + 1 + (Global->Opt->ShowColumnTitles ? 1 : 0), J = m_CurTopFile; + CurY < m_Where.bottom - status_footer_height(); CurY++, J++) + { + int CurColumn = 0; - break; - } + int CurX = m_Where.left + 1; + int Level = 1; - case column_type::size: - case column_type::size_compressed: - case column_type::streams_size: - { - const auto SizeToDisplay = (ColumnType == column_type::size_compressed) - ? m_ListData[ListPos].AllocationSize(this) - : (ColumnType == column_type::streams_size) - ? m_ListData[ListPos].StreamsSize(this) - : m_ListData[ListPos].FileSize; - - Text(FormatStr_Size( - SizeToDisplay, - m_ListData[ListPos].FileName, - m_ListData[ListPos].Attributes, - folder_size_display_type(m_ListData[ListPos].SizeState), - m_ListData[ListPos].ReparseTag, - ColumnType, - Columns[K].type_flags, - ColumnWidth, - m_CurDir)); - break; - } + for (const auto ColumnIndex: std::views::iota(0uz, ColumnCount)) + { + const int ListPos = J + CurColumn * m_Height; + const int ColumnWidth = Columns[ColumnIndex].width; + bool ShowDivider = true; - case column_type::date: - case column_type::time: - case column_type::date_write: - case column_type::date_creation: - case column_type::date_access: - case column_type::date_change: - { - os::chrono::time_point const FileListItem::* FileTime; + if (ColumnWidth < 0) + continue; - switch (ColumnType) - { - case column_type::date_creation: - FileTime = &FileListItem::CreationTime; - break; + GotoXY(CurX, CurY); - case column_type::date_access: - FileTime = &FileListItem::LastAccessTime; - break; + SetShowColor(ListPos); - case column_type::date_change: - FileTime = &FileListItem::ChangeTime; - break; + if (static_cast(ListPos) < ListData.size()) + { + if (!StatusShown && m_CurFile == ListPos) + { + render_status_lines(ListData[m_CurFile], CurrentTime, MaxLeftPos, MinLeftPos); - default: - FileTime = &FileListItem::LastWriteTime; - break; - } + // Restore color for current item and position after status line rendering + SetShowColor(ListPos); - Text(FormatStr_DateTime(std::invoke(FileTime, m_ListData[ListPos]), ColumnType, Columns[K].type_flags, ColumnWidth, CurrentTime)); - break; - } + GotoXY(CurX, CurY); - case column_type::attributes: - { - Text(FormatStr_Attribute(m_ListData[ListPos].Attributes,ColumnWidth)); - break; - } + StatusShown = true; + } + + if (Columns[ColumnIndex].type == column_type::name) + { + int Width = ColumnWidth; - case column_type::description: + const auto ViewFlags = Columns[ColumnIndex].type_flags; + + if ((ViewFlags & COLFLAGS_MARK) && Width > 2) + { + const auto Mark = ListData[ListPos].Selected + ? L"√ "sv + : ViewFlags & COLFLAGS_MARK_DYNAMIC + ? L""sv + : L" "sv; + + Text(Mark); + + Width -= static_cast(Mark.size()); + } + + if (Global->Opt->Highlight && ListData[ListPos].Colors && !ListData[ListPos].Colors->Mark.Mark.empty() && Width > 1) + { + const int MarkLength = static_cast(visual_string_length(ListData[ListPos].Colors->Mark.Mark)); + + if (Width >= MarkLength) { - int CurLeftPos=0; + Width -= MarkLength; - if (!ShowStatus && LeftPos>0) - { - int Length = static_cast(std::wcslen(NullToEmpty(m_ListData[ListPos].DizText))); - if (Length>ColumnWidth) - { - CurLeftPos = std::min(LeftPos, Length-ColumnWidth); - MaxLeftPos = std::max(MaxLeftPos, CurLeftPos); - } - } + const auto OldColor = GetColor(); + SetShowColor(ListPos, false); - auto DizText = m_ListData[ListPos].DizText? m_ListData[ListPos].DizText + CurLeftPos : L""sv; - const auto pos = DizText.find(L'\4'); - if (pos != string::npos) - DizText.remove_suffix(DizText.size() - pos); + Text(ListData[ListPos].Colors->Mark.Mark, MarkLength); - Text(fit_to_left(string(DizText), ColumnWidth)); - break; + SetColor(OldColor); } + } - case column_type::owner: - { - const auto& Owner = m_ListData[ListPos].Owner(this); - size_t Offset = 0; + string_view Name = ListData[ListPos].AlternateOrNormal(m_ShowShortNames); + + const auto NameCopy = Name; + + if (!(ListData[ListPos].Attributes & FILE_ATTRIBUTE_DIRECTORY) && (ViewFlags & COLFLAGS_NOEXTENSION) + || ViewFlags & COLFLAGS_NAMEONLY) + { + Name = name_ext(Name).first; + } - if (!(Columns[K].type_flags & COLFLAGS_FULLOWNER)) + int CurLeftPos = 0; + auto RightAlign = ViewFlags & (COLFLAGS_RIGHTALIGN | COLFLAGS_RIGHTALIGNFORCE); + bool LeftBracket = false, RightBracket = false; + + if (LeftPos) + { + const auto Length = static_cast(visual_string_length(Name)); + + if (Length > Width) + { + if (LeftPos > 0) { - const auto SlashPos = FindSlash(Owner); - if (SlashPos != string::npos) + if (!RightAlign) { - Offset = SlashPos + 1; + CurLeftPos = std::min(LeftPos, Length - Width); + + int ActualLeftPos = 0; + + for (; ActualLeftPos != CurLeftPos && static_cast(visual_string_length(Name)) > Width; ++ActualLeftPos) + { + encoding::utf16::remove_first_codepoint(Name); + } + + MaxLeftPos = std::max(MaxLeftPos, ActualLeftPos); } } - else if(!Owner.empty() && path::is_separator(Owner.front())) + else if (RightAlign) { - Offset = 1; - } + int CurRightPos = LeftPos; - int CurLeftPos=0; + if (Length + CurRightPos < Width) + { + CurRightPos = Width - Length; + } + else + { + RightBracket = true; + LeftBracket = (ViewFlags & COLFLAGS_RIGHTALIGNFORCE) == COLFLAGS_RIGHTALIGNFORCE; + } - if (!ShowStatus && LeftPos>0) - { - int Length = static_cast(Owner.size() - Offset); - if (Length>ColumnWidth) + while (static_cast(visual_string_length(Name)) > Width - CurRightPos) { - CurLeftPos = std::min(LeftPos, Length-ColumnWidth); - MaxLeftPos = std::max(MaxLeftPos, CurLeftPos); + encoding::utf16::remove_first_codepoint(Name); } - } - Text(fit_to_left(Owner.substr(Offset + CurLeftPos), ColumnWidth)); - break; + MinLeftPos = std::min(MinLeftPos, CurRightPos); + RightAlign = false; + } } + } - case column_type::links_number: - { - const auto Value = m_ListData[ListPos].NumberOfLinks(this); - Text(fit_to_right(Value == FileListItem::values::unknown(Value)? L"?"s : str(Value), ColumnWidth)); - break; - } + string StrName; + const auto TooLong = ConvertName(Name, StrName, Width, RightAlign, FALSE, + ListData[ListPos].Attributes); - case column_type::streams_number: - { - const auto Value = m_ListData[ListPos].NumberOfStreams(this); - Text(fit_to_right(Value == FileListItem::values::unknown(Value)? L"?"s : str(Value), ColumnWidth)); - break; - } + if (CurLeftPos) + LeftBracket = true; - default: - break; + if (TooLong) + { + (RightAlign ? LeftBracket : RightBracket) = true; + } + + if (m_ViewSettings.Flags&PVS_FILEUPPERTOLOWERCASE) + if (!(ListData[ListPos].Attributes & FILE_ATTRIBUTE_DIRECTORY) && !IsCaseMixed(NameCopy)) + inplace::lower(StrName); + + if (m_ViewSettings.Flags&PVS_FOLDERUPPERCASE && + (ListData[ListPos].Attributes & FILE_ATTRIBUTE_DIRECTORY)) + inplace::upper(StrName); + + if (m_ViewSettings.Flags&PVS_FILELOWERCASE && + !(ListData[ListPos].Attributes & FILE_ATTRIBUTE_DIRECTORY)) + inplace::lower(StrName); + + Text(StrName, Width); + + if (LeftBracket) + { + if (Level == 1) + SetColor(COL_PANELBOX); + else + HighlightBorder(Level, ListPos); + + GotoXY(CurX - 1, CurY); + + Text(openBracket); } + + if (RightBracket) + { + HighlightBorder(Level, ListPos); + + GotoXY(CurX + ColumnWidth, CurY); + + Text(closeBracket); + + ShowDivider = false; + } + } + else + { + render_column(ListData[ListPos], Columns[ColumnIndex], ColumnWidth, CurrentTime, MaxLeftPos, MinLeftPos); } } else @@ -9078,64 +9177,36 @@ void FileList::ShowList(int ShowStatus,int StartColumn) Text(string(ColumnWidth, L' ')); } - if (ShowDivider==FALSE) - GotoXY(CurX+ColumnWidth+1,CurY); - else + if (ShowDivider) { - if (!ShowStatus) - { - HighlightBorder(Level, ListPos); - } - - if (K == ColumnCount-1) - SetColor(COL_PANELBOX); + HighlightBorder(Level, ListPos); - GotoXY(CurX+ColumnWidth,CurY); + GotoXY(CurX + ColumnWidth, CurY); - if (K==ColumnCount-1) - Text(CurX + ColumnWidth == m_Where.right? BoxSymbols[BS_V2] : L' '); + if (ColumnIndex != ColumnCount - 1) + Text(BoxSymbols[Level == m_ColumnsInStripe ? BS_V2 : BS_V1]); else - Text(ShowStatus? L' ' : BoxSymbols[Level == m_ColumnsInStripe? BS_V2 : BS_V1]); - - if (!ShowStatus) - SetColor(COL_PANELTEXT); + if (CurX + ColumnWidth == m_Where.right) + Text(BoxSymbols[BS_V2]); } - if (!ShowStatus) + if (Level == m_ColumnsInStripe) { - if (Level == m_ColumnsInStripe) - { - Level = 0; - CurColumn++; - } - - Level++; + Level = 0; + CurColumn++; } - } - if ((!ShowStatus || StatusLine) && WhereX() < m_Where.right) - { - SetColor(COL_PANELTEXT); - Text(string(m_Where.right - WhereX(), L' ')); - } - } + Level++; - if (!ShowStatus && !StatusShown && Global->Opt->ShowPanelStatus) - { - SetScreen({ m_Where.left + 1, m_Where.bottom - 1, m_Where.right - 1, m_Where.bottom - 1 }, L' ', colors::PaletteColorToFarColor(COL_PANELTEXT)); - SetColor(COL_PANELTEXT); //??? - //GotoXY(X1+1,Y2-1); - //Text(string(X2 - X1 - 1, L' ')); + CurX += ColumnWidth + 1; + } } - if (!ShowStatus) - { - if (LeftPos<0) - LeftPos=MinLeftPos; + if (LeftPos < 0) + LeftPos = MinLeftPos; - if (LeftPos>0) - LeftPos=MaxLeftPos; - } + if (LeftPos > 0) + LeftPos = MaxLeftPos; } bool FileList::IsModeFullScreen(int Mode) @@ -9165,9 +9236,13 @@ bool FileList::IsDizDisplayed() const bool FileList::IsColumnDisplayed(function_ref const Compare) const { - return - std::ranges::any_of(m_ViewSettings.PanelColumns, Compare) || - std::ranges::any_of(m_ViewSettings.StatusColumns, Compare); + if (std::ranges::any_of(m_ViewSettings.PanelColumns, Compare)) + return true; + + return std::ranges::any_of(m_ViewSettings.StatusLines, [&](const status_line& Line) + { + return std::ranges::any_of(Line, Compare); + }); } bool FileList::IsColumnDisplayed(column_type Type) const diff --git a/far/filelist.hpp b/far/filelist.hpp index b74083c033..0f38260f9e 100644 --- a/far/filelist.hpp +++ b/far/filelist.hpp @@ -267,7 +267,7 @@ class FileList final: public Panel void Scroll(int offset); void CorrectPosition(); void ShowFileList(bool Fast = true); - void ShowList(int ShowStatus, int StartColumn); + void render_file_list(); void SetShowColor(int Position, bool FileColor = true) const; FarColor GetShowColor(int Position, bool FileColor = true) const; void ShowSelectedSize(); @@ -321,6 +321,15 @@ class FileList final: public Panel static void FillParentPoint(FileListItem& Item); + int visible_status_line_count() const; + int status_footer_height() const; + int status_separator_y() const; + int status_text_top_y() const; + + void render_column(const FileListItem& Item, const column& Column, int ColumnWidth, + os::chrono::time_point CurrentTime, int& MaxLeftPos, int& MinLeftPos) const; + void render_status_lines(const FileListItem& Item, os::chrono::time_point CurrentTime, int& MaxLeftPos, int& MinLeftPos) const; + std::unique_ptr m_Filter; DizList Diz; bool DizRead{}; diff --git a/far/panel.cpp b/far/panel.cpp index 1e9814ef65..842d6a8f8b 100644 --- a/far/panel.cpp +++ b/far/panel.cpp @@ -1149,3 +1149,55 @@ panel_sort plugin_sort_mode_to_internal(int const Mode) { return panel_sort{ Mode < SM_USER? Mode - PluginSortModesOffset : Mode }; } + +#ifdef ENABLE_TESTS + +#include "testing.hpp" + +TEST_CASE("panel.PanelViewSettingsClone") +{ + { + PanelViewSettings Settings; + Settings.PanelColumns = + { + { column_type::name, COLFLAGS_NONE, 42, }, + }; + Settings.Name = L"test"sv; + Settings.Flags = PVS_FULLSCREEN; + + const auto Clone = Settings.clone(); + + REQUIRE(Clone.PanelColumns.size() == 1uz); + REQUIRE(Clone.PanelColumns[0].type == column_type::name); + REQUIRE(Clone.StatusLines.empty()); + REQUIRE(Clone.Name == L"test"sv); + REQUIRE(Clone.Flags == PVS_FULLSCREEN); + } + + { + PanelViewSettings Settings; + Settings.StatusLines = + { + { + { column_type::name, COLFLAGS_RIGHTALIGN, 10, }, + }, + { + { column_type::size, COLFLAGS_NONE, 5, }, + }, + }; + + const auto Clone = Settings.clone(); + + REQUIRE(Clone.StatusLines.size() == 2uz); + REQUIRE(Clone.StatusLines[0].size() == 1uz); + REQUIRE(Clone.StatusLines[0][0].type == column_type::name); + REQUIRE(Clone.StatusLines[0][0].type_flags == COLFLAGS_RIGHTALIGN); + REQUIRE(Clone.StatusLines[0][0].width == 10); + REQUIRE(Clone.StatusLines[1].size() == 1uz); + REQUIRE(Clone.StatusLines[1][0].type == column_type::size); + REQUIRE(Clone.StatusLines[1][0].type_flags == COLFLAGS_NONE); + REQUIRE(Clone.StatusLines[1][0].width == 5); + } +} + +#endif diff --git a/far/panel.hpp b/far/panel.hpp index 5d910ba817..879b27f400 100644 --- a/far/panel.hpp +++ b/far/panel.hpp @@ -65,6 +65,8 @@ struct column string title; }; +using status_line = std::vector; + struct PanelViewSettings { NONCOPYABLE(PanelViewSettings); @@ -76,14 +78,14 @@ struct PanelViewSettings { PanelViewSettings result; result.PanelColumns = PanelColumns; - result.StatusColumns = StatusColumns; + result.StatusLines = StatusLines; result.Name = Name; result.Flags = Flags; return result; } std::vector PanelColumns; - std::vector StatusColumns; + std::vector StatusLines; string Name; unsigned Flags; }; diff --git a/far/panelmix.cpp b/far/panelmix.cpp index 94459824e1..c11239ff41 100644 --- a/far/panelmix.cpp +++ b/far/panelmix.cpp @@ -97,6 +97,218 @@ ColumnInfo[] static_assert(std::size(ColumnInfo) == static_cast(column_type::count)); +std::vector split_status_lines(string_view const ColumnTitles) +{ + std::vector Columns; + bool InCustomColumn{}; + size_t Start{}; + + for (size_t i = 0; i != ColumnTitles.size(); ++i) + { + switch (ColumnTitles[i]) + { + case L'<': + InCustomColumn = true; + break; + + case L'>': + InCustomColumn = false; + break; + + case L'|': + if (!InCustomColumn) + { + Columns.emplace_back(ColumnTitles.substr(Start, i - Start)); + Start = i + 1; + } + + break; + + default: + break; + } + } + + Columns.emplace_back(ColumnTitles.substr(Start)); + + return Columns; +} + +std::vector extract_status_widths(string_view const ColumnWidths) +{ + std::vector Widths; + + for (const auto& Line: enum_tokens(ColumnWidths, L"|"sv)) + { + if (Line.empty()) + continue; + + for (const auto& Token: enum_tokens(Line, L","sv)) + { + Widths.emplace_back(Token); + } + } + + return Widths; +} + +void deserialize_column_widths(std::vector& Columns, std::span const WidthTokens, size_t* WidthsConsumed = nullptr) +{ + size_t WidthIndex{}; + + for (auto& i: Columns) + { + const auto Width = WidthIndex != WidthTokens.size()? WidthTokens[WidthIndex++] : L""sv; + + if (!Width.empty() && !from_string(Width, i.width)) + { + LOGWARNING(L"Incorrect column width {}"sv, Width); + } + + i.width_type = fixed; + + if (Width.size() > 1) + { + switch (Width.back()) + { + case L'%': + i.width_type = percent; + break; + + default: + break; + } + } + } + + if (WidthsConsumed) + *WidthsConsumed = WidthIndex; +} + +std::vector deserialize_columns(string_view const ColumnTitles) +{ + std::vector Columns; + + for (const auto& Type: enum_tokens(ColumnTitles, L","sv)) + { + if (Type.empty()) + continue; + + column NewColumn{}; + + if (Type.front() == L'N') + { + NewColumn.type = column_type::name; + + for (const auto& i: Type.substr(1)) + { + switch (i) + { + case L'M': NewColumn.type_flags |= COLFLAGS_MARK; break; + case L'D': NewColumn.type_flags |= COLFLAGS_MARK_DYNAMIC; break; + case L'O': NewColumn.type_flags |= COLFLAGS_NAMEONLY; break; + case L'R': NewColumn.type_flags |= COLFLAGS_RIGHTALIGN; break; + case L'F': NewColumn.type_flags |= COLFLAGS_RIGHTALIGNFORCE; break; + case L'N': NewColumn.type_flags |= COLFLAGS_NOEXTENSION; break; + } + } + } + else if (Type.front() == L'S' || Type.front() == L'P' || Type.front() == L'G') + { + NewColumn.type = Type.front() == L'S'? + column_type::size : + Type.front() == L'P'? + column_type::size_compressed : + column_type::streams_size; + + for (const auto& i: Type.substr(1)) + { + switch (i) + { + case L'C': NewColumn.type_flags |= COLFLAGS_GROUPDIGITS; break; + case L'E': NewColumn.type_flags |= COLFLAGS_ECONOMIC; break; + case L'F': NewColumn.type_flags |= COLFLAGS_FLOATSIZE; break; + case L'T': NewColumn.type_flags |= COLFLAGS_THOUSAND; break; + } + } + } + else if ( + Type.starts_with(L"DM"sv) || + Type.starts_with(L"DC"sv) || + Type.starts_with(L"DA"sv) || + Type.starts_with(L"DE"sv)) + { + switch (Type[1]) + { + case L'M': NewColumn.type = column_type::date_write; break; + case L'C': NewColumn.type = column_type::date_creation; break; + case L'A': NewColumn.type = column_type::date_access; break; + case L'E': NewColumn.type = column_type::date_change; break; + } + + for (const auto& i: Type.substr(2)) + { + switch (i) + { + case L'B': NewColumn.type_flags |= COLFLAGS_BRIEF; break; + case L'M': NewColumn.type_flags |= COLFLAGS_MONTH; break; + } + } + } + else if (Type.front() == L'O') + { + NewColumn.type = column_type::owner; + + if (Type.size() > 1 && Type[1] == L'L') + NewColumn.type_flags |= COLFLAGS_FULLOWNER; + } + else if (Type.front() == L'X') + { + NewColumn.type = column_type::extension; + + if (Type.size() > 1 && Type[1] == L'R') + NewColumn.type_flags |= COLFLAGS_RIGHTALIGN; + } + else if (Type.size() > 2 && Type.front() == L'<' && Type.back() == L'>') + { + NewColumn.title = Type.substr(1, Type.size() - 2); + NewColumn.type = column_type::custom_0; + } + else + { + const auto ItemIterator = std::ranges::find(ColumnInfo, Type, &column_info::String); + if (ItemIterator != std::cend(ColumnInfo)) + NewColumn.type = ItemIterator->Type; + else if (Type.size() >= 2 && Type.size() <= 3 && Type.front() == L'C') + { + size_t Index; + if (from_string(Type.substr(1), Index)) + NewColumn.type = static_cast(static_cast(column_type::custom_0) + Index); + else + { + LOGWARNING(L"Incorrect custom column {}"sv, Type); + // TODO: error message? + } + } + else + { + LOGWARNING(L"Unknown column type {}"sv, Type); + // TODO: error message? + continue; + } + } + + Columns.emplace_back(NewColumn); + } + + if (Columns.empty()) + { + Columns.emplace_back(); + } + + return Columns; +} + void ShellUpdatePanels(panel_ptr SrcPanel, bool NeedSetUpADir) { if (!SrcPanel) @@ -313,163 +525,50 @@ bool MakePathForUI(DWORD Key, string &strPathName) std::vector DeserialiseViewSettings(string_view const ColumnTitles, string_view const ColumnWidths) { // BUGBUG, add error checking + auto Columns = deserialize_columns(ColumnTitles); - std::vector Columns; + std::vector WidthTokens; - for (const auto& Type: enum_tokens(ColumnTitles, L","sv)) + for (const auto& Token: enum_tokens(ColumnWidths, L","sv)) { - if (Type.empty()) - continue; - - column NewColumn{}; - - if (Type.front() == L'N') - { - NewColumn.type = column_type::name; - - for (const auto& i: Type.substr(1)) - { - switch (i) - { - case L'M': NewColumn.type_flags |= COLFLAGS_MARK; break; - case L'D': NewColumn.type_flags |= COLFLAGS_MARK_DYNAMIC; break; - case L'O': NewColumn.type_flags |= COLFLAGS_NAMEONLY; break; - case L'R': NewColumn.type_flags |= COLFLAGS_RIGHTALIGN; break; - case L'F': NewColumn.type_flags |= COLFLAGS_RIGHTALIGNFORCE; break; - case L'N': NewColumn.type_flags |= COLFLAGS_NOEXTENSION; break; - } - } - } - else if (Type.front() == L'S' || Type.front() == L'P' || Type.front() == L'G') - { - NewColumn.type = Type.front() == L'S'? - column_type::size : - Type.front() == L'P'? - column_type::size_compressed : - column_type::streams_size; - - for (const auto& i: Type.substr(1)) - { - switch (i) - { - case L'C': NewColumn.type_flags |= COLFLAGS_GROUPDIGITS; break; - case L'E': NewColumn.type_flags |= COLFLAGS_ECONOMIC; break; - case L'F': NewColumn.type_flags |= COLFLAGS_FLOATSIZE; break; - case L'T': NewColumn.type_flags |= COLFLAGS_THOUSAND; break; - } - } - } - else if ( - Type.starts_with(L"DM"sv) || - Type.starts_with(L"DC"sv) || - Type.starts_with(L"DA"sv) || - Type.starts_with(L"DE"sv)) - { - switch (Type[1]) - { - case L'M': NewColumn.type = column_type::date_write; break; - case L'C': NewColumn.type = column_type::date_creation; break; - case L'A': NewColumn.type = column_type::date_access; break; - case L'E': NewColumn.type = column_type::date_change; break; - } + WidthTokens.emplace_back(Token); + } - for (const auto& i: Type.substr(2)) - { - switch (i) - { - case L'B': NewColumn.type_flags |= COLFLAGS_BRIEF; break; - case L'M': NewColumn.type_flags |= COLFLAGS_MONTH; break; - } - } - } - else if (Type.front() == L'O') - { - NewColumn.type = column_type::owner; + deserialize_column_widths(Columns, WidthTokens); - if (Type.size() > 1 && Type[1] == L'L') - NewColumn.type_flags |= COLFLAGS_FULLOWNER; - } - else if (Type.front() == L'X') - { - NewColumn.type = column_type::extension; + return Columns; +} - if (Type.size() > 1 && Type[1] == L'R') - NewColumn.type_flags |= COLFLAGS_RIGHTALIGN; - } - else if (Type.size() > 2 && Type.front() == L'<' && Type.back() == L'>') - { - NewColumn.title = Type.substr(1, Type.size() - 2); - NewColumn.type = column_type::custom_0; - } - else - { - const auto ItemIterator = std::ranges::find(ColumnInfo, Type, &column_info::String); - if (ItemIterator != std::cend(ColumnInfo)) - NewColumn.type = ItemIterator->Type; - else if (Type.size() >= 2 && Type.size() <= 3 && Type.front() == L'C') - { - size_t Index; - if (from_string(Type.substr(1), Index)) - NewColumn.type = static_cast(static_cast(column_type::custom_0) + Index); - else - { - LOGWARNING(L"Incorrect custom column {}"sv, Type); - // TODO: error message? - } - } - else - { - LOGWARNING(L"Unknown column type {}"sv, Type); - // TODO: error message? - continue; - } - } +std::vector> deserialize_status_line_settings(string_view const ColumnTitles, string_view const ColumnWidths) +{ + std::vector> Lines; - Columns.emplace_back(NewColumn); - } + const auto Widths = extract_status_widths(ColumnWidths); - enum_tokens const EnumWidths(ColumnWidths, L","sv); - std::ranges::subrange EnumWidthsRange(EnumWidths); + size_t WidthIndex{}; - for (auto& i: Columns) + for (const auto& Line: split_status_lines(ColumnTitles)) { - auto Width = L""sv; - - if (!EnumWidthsRange.empty()) + if (Line.empty()) { - Width = *EnumWidthsRange.begin(); - EnumWidthsRange.advance(1); + Lines.emplace_back(); + continue; } - // "column types" is a determinant here (see the loop header) so we can't break or continue here - - // if "column sizes" ends earlier or if user entered two commas we just use default size. - if (!Width.empty() && !from_string(Width, i.width)) - { - LOGWARNING(L"Incorrect column width {}"sv, Width); - } + auto Columns = deserialize_columns(Line); - i.width_type = col_width::fixed; + size_t WidthsConsumed{}; - if (Width.size()>1) - { - switch (Width.back()) - { - case L'%': - i.width_type = col_width::percent; - break; - } - } - } + deserialize_column_widths(Columns, std::span(Widths).subspan(WidthIndex), &WidthsConsumed); - if (Columns.empty()) - { - Columns.emplace_back(); + WidthIndex += WidthsConsumed; + + Lines.emplace_back(std::move(Columns)); } - return Columns; + return Lines; } - std::pair SerialiseViewSettings(const std::vector& Columns) { std::pair Result; @@ -587,6 +686,28 @@ std::pair SerialiseViewSettings(const std::vector& Colum return Result; } +std::pair serialize_status_line_settings(const std::vector>& Lines) +{ + std::pair Result; + auto& [ColumnTitles, ColumnWidths] = Result; + + for (size_t Index = 0; Index != Lines.size(); ++Index) + { + if (Index) + { + ColumnTitles += L'|'; + ColumnWidths += L'|'; + } + + const auto [LineTitles, LineWidths] = SerialiseViewSettings(Lines[Index]); + + ColumnTitles += LineTitles; + ColumnWidths += LineWidths; + } + + return Result; +} + string FormatStr_Attribute(os::fs::attributes FileAttributes, size_t const Width) { string OutStr; @@ -789,3 +910,99 @@ int GetDefaultWidth(const column& Column) } return Width; } + +#if ENABLE_TESTS + +#include "testing.hpp" + +TEST_CASE("panelmix.StatusViewSettings") +{ + const auto CheckNameEmptySize = [](string_view const Widths) + { + const auto Lines = deserialize_status_line_settings(L"N||S"sv, Widths); + + REQUIRE(Lines.size() == 3uz); + REQUIRE(Lines[0].size() == 1uz); + REQUIRE(Lines[0][0].type == column_type::name); + REQUIRE(Lines[0][0].width == 10); + + REQUIRE(Lines[1].empty()); + + REQUIRE(Lines[2].size() == 1uz); + REQUIRE(Lines[2][0].type == column_type::size); + REQUIRE(Lines[2][0].width == 5); + + const auto [ColumnTitles, ColumnWidths] = serialize_status_line_settings(Lines); + REQUIRE(ColumnTitles == L"N||S"sv); + REQUIRE(ColumnWidths == L"10||5"sv); + }; + + CheckNameEmptySize(L"10,5"sv); + CheckNameEmptySize(L"10|5"sv); + CheckNameEmptySize(L"10||5"sv); + + { + const auto Lines = deserialize_status_line_settings(L"||N"sv, L"7,8"sv); + + REQUIRE(Lines.size() == 3uz); + REQUIRE(Lines[0].size() == 1uz); + REQUIRE(Lines[0][0].type == column_type::custom_0); + REQUIRE(Lines[0][0].title == L"foo|bar"sv); + REQUIRE(Lines[0][0].width == 7); + REQUIRE(Lines[1].empty()); + REQUIRE(Lines[2].size() == 1uz); + REQUIRE(Lines[2][0].type == column_type::name); + REQUIRE(Lines[2][0].width == 8); + + const auto [ColumnTitles, ColumnWidths] = serialize_status_line_settings(Lines); + REQUIRE(ColumnTitles == L"||N"sv); + REQUIRE(ColumnWidths == L"7||8"sv); + } + + { + const auto Lines = deserialize_status_line_settings(L"||"sv, L"10,5"sv); + REQUIRE(Lines.size() == 3uz); + REQUIRE(std::ranges::all_of(Lines, std::mem_fn(&status_line::empty))); + + const auto [ColumnTitles, ColumnWidths] = serialize_status_line_settings(Lines); + REQUIRE(ColumnTitles == L"||"sv); + REQUIRE(ColumnWidths == L"||"sv); + } + + { + const auto Lines = deserialize_status_line_settings(L"N|S"sv, L"10%,5,7"sv); + REQUIRE(Lines.size() == 2uz); + REQUIRE(Lines[0].size() == 1uz); + REQUIRE(Lines[0][0].width == 10); + REQUIRE(Lines[0][0].width_type == col_width::percent); + REQUIRE(Lines[1].size() == 1uz); + REQUIRE(Lines[1][0].width == 5); + + const auto [ColumnTitles, ColumnWidths] = serialize_status_line_settings(Lines); + REQUIRE(ColumnTitles == L"N|S"sv); + REQUIRE(ColumnWidths == L"10%|5"sv); + } + + { + const auto Lines = deserialize_status_line_settings(L"N|S"sv, L"10"sv); + REQUIRE(Lines.size() == 2uz); + REQUIRE(Lines[0][0].width == 10); + REQUIRE(Lines[1][0].width == 0); + + const auto [ColumnTitles, ColumnWidths] = serialize_status_line_settings(Lines); + REQUIRE(ColumnTitles == L"N|S"sv); + REQUIRE(ColumnWidths == L"10|0"sv); + } + + { + const auto Lines = deserialize_status_line_settings(L""sv, L""sv); + REQUIRE(Lines.size() == 1uz); + REQUIRE(Lines.front().empty()); + + const auto [ColumnTitles, ColumnWidths] = serialize_status_line_settings(Lines); + REQUIRE(ColumnTitles.empty()); + REQUIRE(ColumnWidths.empty()); + } +} + +#endif diff --git a/far/panelmix.hpp b/far/panelmix.hpp index 341c9170dc..626e4e079d 100644 --- a/far/panelmix.hpp +++ b/far/panelmix.hpp @@ -73,5 +73,7 @@ string FormatStr_Size(long long Size, string_view strName, std::vector DeserialiseViewSettings(string_view ColumnTitles, string_view ColumnWidths); std::pair SerialiseViewSettings(const std::vector& Columns); int GetDefaultWidth(const column& Column); +std::vector> deserialize_status_line_settings(string_view ColumnTitles, string_view ColumnWidths); +std::pair serialize_status_line_settings(const std::vector>& Lines); #endif // PANELMIX_HPP_AF7AAF02_56C0_4E41_B1D9_D1F1A5B4025D