27#include "table/strings.h"
34#if defined(WITH_LIBLZMA)
90 this->parent = parent;
93void TextfileWindow::ConstructWindow()
98 this->GetWidget<NWidgetCore>(
WID_TF_CAPTION)->SetStringTip(STR_TEXTFILE_README_CAPTION + this->
file_type, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
130 size.width = std::max(200u, size.width);
148static const std::regex
_markdown_link_regex{
"\\[(.+?)\\]\\((.+?)\\)", std::regex_constants::ECMAScript | std::regex_constants::optimize};
189 for (
char c : line) {
192 if (c ==
'#')
continue;
193 if (c ==
' ')
continue;
199 if (c ==
' ' || c ==
'-')
continue;
210 }
else if (c ==
' ' || c ==
'-') {
227 std::string::const_iterator last_match_end = line.
text.cbegin();
228 std::string fixed_line;
232 while (matcher != std::sregex_iterator()) {
233 std::smatch match = *matcher;
235 Hyperlink &link = this->
links.emplace_back(line_index, 0, 0, match[2].str());
241 link_colour = SCC_GREEN;
244 link_colour = SCC_LTBLUE;
247 link_colour = SCC_LTBROWN;
251 link_colour = SCC_CONTROL_END;
255 if (link_colour != SCC_CONTROL_END) {
257 builder += std::string_view(last_match_end, match[0].first);
258 link.
begin = fixed_line.length();
259 builder.
PutUtf8(SCC_PUSH_COLOUR);
261 builder += match[1].str();
262 link.
end = fixed_line.length();
263 builder.
PutUtf8(SCC_POP_COLOUR);
264 last_match_end = match[0].second;
270 if (last_match_end == line.
text.cbegin())
return;
273 fixed_line += std::string(last_match_end, line.
text.cend());
276 line.
text = std::move(fixed_line);
286 if (this->
links.empty())
return nullptr;
291 int visible_line = 0;
292 auto it = std::ranges::find_if(this->
lines, [&visible_line, clicked_row](
const Line &l) {
294 return (visible_line - l.
num_lines) <= clicked_row && visible_line > clicked_row;
296 if (it == this->
lines.cend())
return nullptr;
298 size_t line_index = it - this->
lines.cbegin();
299 size_t subline = clicked_row - (visible_line - it->num_lines);
300 Debug(misc, 4,
"TextfileWindow check hyperlink: clicked_row={}, line_index={}, line.top={}, subline={}", clicked_row, line_index, visible_line - it->num_lines, subline);
303 std::vector<const Hyperlink *> found_links;
304 for (
const auto &link : this->
links) {
305 if (link.line == line_index) found_links.push_back(&link);
307 if (found_links.empty())
return nullptr;
310 const Line &line = this->
lines[line_index];
312 assert(subline < layout.size());
314 if (char_index < 0)
return nullptr;
315 Debug(misc, 4,
"TextfileWindow check hyperlink click: line={}, subline={}, char_index={}", line_index, subline, char_index);
318 for (
const Hyperlink *link : found_links) {
319 Debug(misc, 4,
"Checking link from char {} to {}", link->begin, link->end);
320 if (
static_cast<size_t>(char_index) >= link->begin &&
static_cast<size_t>(char_index) < link->end) {
321 Debug(misc, 4,
"Returning link with destination: {}", link->destination);
336 this->
history.erase(this->
history.begin() + this->history_pos + 1, this->history.end());
338 this->
history.emplace_back(filepath, 0);
359 if (delta == 0)
return;
360 if (delta < 0 &&
static_cast<int>(this->
history_pos) < -delta)
return;
387 this->ScrollToLine(it->line);
416 if (!newfile.starts_with(
"./"))
return;
419 std::string newpath = this->
filepath;
420 size_t pos = newpath.find_last_of(PATHSEPCHAR);
421 if (pos == std::string::npos) {
424 newpath.erase(pos + 1);
428 size_t anchor_pos = newfile.find_first_of(
'#');
430 if (anchor_pos != std::string::npos) {
431 anchor = newfile.substr(anchor_pos);
432 newfile.erase(anchor_pos);
439 newfile = newfile.substr(2);
440 if (PATHSEPCHAR !=
'/') {
441 for (
char &c : newfile) {
442 if (c ==
'/') c = PATHSEPCHAR;
447 newpath = newpath + newfile;
455 this->
filename = newpath.substr(newpath.find_last_of(PATHSEP) + 1);
462 if (anchor.empty() || line != 0) {
463 this->ScrollToLine(line);
467 this->ScrollToLine(anchor_dest->line);
470 this->ScrollToLine(0);
489 for (
size_t line_index = 0; line_index < this->
lines.size(); ++line_index) {
496 if (!line.
text.empty() && line.
text[0] ==
'#') {
497 this->
jumplist.push_back(line_index);
498 this->
lines[line_index].colour = TC_GOLD;
514 for (
size_t line : this->
jumplist) {
515 list.push_back(MakeDropDownListStringItem(
GetString(STR_TEXTFILE_JUMPLIST_ITEM, this->
lines[line].text), (
int)line));
542 if (link ==
nullptr)
return false;
576 for (
auto &line : this->
lines) {
578 cur_line += line.num_lines;
579 if (cur_line <= pos)
continue;
580 if (
top > pos + cap)
break;
582 int y_offset = (
top - pos) * line_height;
583 if (line.wrapped_width != 0) {
597 this->UpdateVisibleIterators();
607 for (
auto &line : this->
lines) {
610 line.wrapped_width = 0;
618 if (!gui_scope)
return;
628 this->ScrollToLine(index);
631extern bool CanContinueRealtimeTick();
639 bool wrapped = this->IsTextWrapped();
647 int old_lines = line.num_lines;
649 if (line.wrapped_width != window_width) {
651 line.wrapped_width = window_width;
654 if (line.max_width == -1) {
659 line.wrapped_width = 0;
663 this->
num_lines += (line.num_lines - old_lines);
666 if (this->
visible_first > it) pos += (line.num_lines - old_lines);
671 if (!CanContinueRealtimeTick())
break;
681 auto r = this->ContinueReflow();
688 this->UpdateVisibleIterators();
695void TextfileWindow::UpdateVisibleIterators()
702 this->visible_last = std::ranges::next(this->
visible_first, cap + 1, std::end(this->
lines));
709 this->UpdateVisibleIterators();
713std::vector<TextfileWindow::Line>::iterator TextfileWindow::GetIteratorFromPosition(
int pos)
715 for (
auto it = std::begin(this->
lines); it != std::end(this->
lines); ++it) {
716 pos -= it->num_lines;
717 if (pos <= 0)
return it;
719 return std::end(this->
lines);
722void TextfileWindow::ScrollToLine(
size_t line)
726 for (
auto it = std::begin(this->
lines); it != std::end(this->
lines) && line > 0; --line, ++it) {
727 newpos += it->num_lines;
730 this->UpdateVisibleIterators();
735bool TextfileWindow::IsTextWrapped()
const
764#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
770#if defined(WITH_ZLIB)
780static std::vector<char>
Gunzip(std::span<char> input)
782 static const int BLOCKSIZE = 8192;
783 std::vector<char> output;
786 z.next_in =
reinterpret_cast<Bytef *
>(input.data());
787 z.avail_in =
static_cast<uInt
>(input.size());
790 int res = inflateInit2(&z, 15 + 32);
792 while (res == Z_OK || (res == Z_BUF_ERROR && z.avail_out == 0)) {
795 z.avail_out += BLOCKSIZE;
796 output.resize(output.size() + BLOCKSIZE);
797 z.next_out =
reinterpret_cast<Bytef *
>(output.data() + output.size() - z.avail_out);
798 res = inflate(&z, Z_FINISH);
802 if (res != Z_STREAM_END)
return {};
804 output.resize(output.size() - z.avail_out);
780static std::vector<char>
Gunzip(std::span<char> input) {
…}
809#if defined(WITH_LIBLZMA)
819static std::vector<char>
Xunzip(std::span<char> input)
821 static const int BLOCKSIZE = 8192;
822 std::vector<char> output;
824 lzma_stream z = LZMA_STREAM_INIT;
825 z.next_in =
reinterpret_cast<uint8_t *
>(input.data());
826 z.avail_in = input.size();
828 int res = lzma_auto_decoder(&z, UINT64_MAX, LZMA_CONCATENATED);
830 while (res == LZMA_OK || (res == LZMA_BUF_ERROR && z.avail_out == 0)) {
833 z.avail_out += BLOCKSIZE;
834 output.resize(output.size() + BLOCKSIZE);
835 z.next_out =
reinterpret_cast<uint8_t *
>(output.data() + output.size() - z.avail_out);
836 res = lzma_code(&z, LZMA_FINISH);
840 if (res != LZMA_STREAM_END)
return {};
842 output.resize(output.size() - z.avail_out);
819static std::vector<char>
Xunzip(std::span<char> input) {
…}
858 if (textfile.empty())
return;
862 auto handle =
FioFOpenFile(textfile,
"rb", dir, &filesize);
863 if (!handle.has_value())
return;
865 if (filesize == 0)
return;
867 std::vector<char> buf;
868 buf.resize(filesize);
869 size_t read = fread(buf.data(), 1, buf.size(), *handle);
871 if (read != buf.size())
return;
873#if defined(WITH_ZLIB)
875 if (textfile.ends_with(
".gz")) buf =
Gunzip(buf);
878#if defined(WITH_LIBLZMA)
880 if (textfile.ends_with(
".xz")) buf =
Xunzip(buf);
883 if (buf.empty())
return;
885 std::string_view sv_buf(buf.data(), buf.size());
888 if (sv_buf.starts_with(
"\ufeff")) sv_buf.remove_prefix(3);
913 std::string_view p(text);
914 auto next = p.find_first_of(
'\n');
915 while (next != std::string_view::npos) {
916 this->
lines.emplace_back(p.substr(0, next));
917 p.remove_prefix(next + 1);
919 next = p.find_first_of(
'\n');
921 this->
lines.emplace_back(p);
941 static const std::string_view prefixes[] = {
946 static_assert(
lengthof(prefixes) == TFT_CONTENT_END);
949 if (type >= TFT_CONTENT_END)
return std::nullopt;
951 std::string_view prefix = prefixes[type];
953 if (filename.empty())
return std::nullopt;
955 auto slash = filename.find_last_of(PATHSEPCHAR);
956 if (slash == std::string::npos)
return std::nullopt;
958 std::string_view base_path = filename.substr(0, slash + 1);
960 static const std::initializer_list<const std::string_view> extensions{
963#if defined(WITH_ZLIB)
967#if defined(WITH_LIBLZMA)
973 for (
auto &extension : extensions) {
980 file_path = fmt::format(
"{}{}.{}", base_path, prefix, extension);
Class for backupping variables and making sure they are restored later.
void PutUtf8(char32_t c)
Append UTF.8 char.
The layouter performs all the layout work.
ptrdiff_t GetCharAtPosition(int x, size_t line_index) const
Get the character that is at a pixel position in the first line of the layouted text.
Compose data into a growing std::string.
Control codes that are embedded in the translation strings.
StringControlCode
List of string control codes used for string formatting, displaying, and by strgen to generate the la...
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
void ShowDropDownList(Window *w, DropDownList &&list, int selected, WidgetID button, uint width, bool instant_close, bool persist)
Show a drop down list.
Functions related to the drop down widget.
Types related to the drop down widget.
std::vector< std::unique_ptr< const DropDownListItem > > DropDownList
A drop down list is a collection of drop down list items.
std::optional< FileHandle > FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
bool FioCheckFileExists(std::string_view filename, Subdirectory subdir)
Check whether the given file exists.
Functions for Standard In/Out file operations.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
@ NO_DIRECTORY
A path without any base directory.
fluid_settings_t * settings
FluidSynth settings handle.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Functions to read fonts from files and cache them.
int GetStringHeight(std::string_view str, int maxw, FontSize fontsize)
Calculates height of string (in pixels).
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
int DrawString(int left, int right, int top, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
bool DrawStringMultiLineWithClipping(int left, int right, int top, int bottom, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw a multiline string, possibly over multiple lines, if the region is within the current display cl...
bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
Set up a clipping area for only drawing into a certain area.
Functions related to the gfx engine.
Functions related to laying out the texts.
Types related to the graphics and/or input devices.
FontSize
Available font sizes.
@ FS_MONO
Index of the monospaced font in the font tables.
@ SA_TOP
Top align the text.
@ SA_LEFT
Left align the text.
@ FILLRECT_CHECKER
Draw only every second pixel, used for greying-out.
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
constexpr uint CeilDiv(uint a, uint b)
Computes ceil(a / b) for non-negative a and b.
void GuiShowTooltips(Window *parent, EncodedString &&text, TooltipCloseCondition close_tooltip)
Shows a tooltip.
static const uint8_t PC_WHITE
White palette colour.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
bool StrEndsWithIgnoreCase(std::string_view str, std::string_view suffix)
Check whether the given string ends with the given suffix, ignoring case.
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Compose strings from textual and binary data.
Functions related to low-level strings.
@ ReplaceWithQuestionMark
Replace the unknown/bad bits with question marks.
@ AllowNewline
Allow newlines; replaces '\r ' with ' ' during processing.
@ ReplaceTabCrNlWithSpace
Replace tabs ('\t'), carriage returns ('\r') and newlines (' ') with spaces.
void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
Check whether the currently loaded language pack uses characters that the currently loaded font does ...
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
std::string_view GetCurrentLanguageIsoCode()
Get the ISO language code of the currently loaded language.
TextDirection _current_text_dir
Text direction of the currently selected language.
@ TD_RTL
Text is written right-to-left by default.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
Dimensions (a width and height) of a rectangle in 2D.
Data about how and where to blit pixels.
Settings for the four different fonts.
Coordinates of a point in 2D.
constexpr uint Horizontal() const
Get total horizontal padding of RectPadding.
constexpr uint Vertical() const
Get total vertical padding of RectPadding.
Specification of a rectangle with absolute coordinates of all edges.
Rect WithWidth(int width, bool end) const
Copy Rect and set its width.
int Width() const
Get width of Rect.
Rect Shrink(int s) const
Copy and shrink Rect by s pixels.
Rect WithHeight(int height, bool end=false) const
Copy Rect and set its height.
Rect Translate(int x, int y) const
Copy and translate Rect by x,y pixels.
uint step_width
Step-size of width resize changes.
size_t end
Character position on line the link end.
size_t begin
Character position on line the link begins.
std::string destination
Destination for the link.
int num_lines
Number of visual lines for this line.
std::string text
Contents of the line.
Scrollbar * hscroll
Horizontal scrollbar.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
LineIterator visible_first
Iterator to first visible element.
LineIterator visible_last
Iterator to last visible element.
void Reset() override
Reset the search, i.e.
ReflowIterator reflow_iter
Current iterator for reflow.
uint search_iterator
Iterator for the font check search.
void SetupScrollbars()
Set scrollbars to the right lengths.
void AppendHistory(const std::string &filepath)
Append the new location to the history, so the user can go back.
void OnDropdownSelect(WidgetID widget, int index) override
A dropdown option associated to this window has been selected.
bool Monospace() override
Whether to search for a monospace font or not.
const Hyperlink * GetHyperlink(Point pt) const
Get the hyperlink at the given position.
void NavigateToFile(std::string newfile, size_t line)
Navigate to the requested file.
std::optional< std::string_view > NextString() override
Get the next string to search through.
ReflowIterator reflow_end
End iterator for reflow.
std::vector< Line > lines
#text, split into lines in a table with lines.
void LoadText(std::string_view buf)
Load a text into the textfile viewer.
bool OnTooltip(Point pt, WidgetID widget, TooltipCloseCondition close_cond) override
Event to display a custom tooltip.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
void UpdateHistoryScrollpos()
Update the scroll position to the current, so we can restore there if we go back.
virtual void AfterLoadText()
Post-processing after the text is loaded.
std::string filepath
Full path to the filename.
void OnScrollbarScroll(WidgetID widget) override
Notify window that a scrollbar position has been updated.
TextfileType file_type
Type of textfile to view.
size_t num_lines
Number of lines of text, taking account of wrapping.
void AfterLoadMarkdown()
Post-processing of markdown files.
FontSize DefaultSize() override
Get the default (font) size of the string.
void OnInit() override
Notification that the nested widget tree gets initialized.
void SetFontNames(FontCacheSettings *settings, std::string_view font_name, const void *os_data) override
Set the right font names.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
void OnRealtimeTick(uint delta_ms) override
Called periodically.
size_t history_pos
Position in browsing history (for forward movement).
std::vector< Hyperlink > link_anchors
Anchor names of headings that can be linked to.
void NavigateHistory(int delta)
Navigate through the history, either forward or backward.
virtual void OnHyperlinkClick(const Hyperlink &link)
Handle the clicking on a hyperlink.
std::vector< HistoryEntry > history
Browsing history in this window.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
std::vector< Hyperlink > links
Clickable links in lines.
void ReflowContent()
Reset the reflow process to start on the next UI tick.
@ VisibleReflowed
Visible content has been reflowed.
@ None
Nothing has been reflowed.
@ Reflowed
Content has been reflowed.
std::string filename
Filename of the textfile.
Scrollbar * vscroll
Vertical scrollbar.
virtual void LoadTextfile(const std::string &textfile, Subdirectory dir)
Loads the textfile text from file and setup lines.
int max_width
Maximum length of unwrapped text line.
void OnResize() override
Called after the window got resized.
std::vector< size_t > jumplist
Table of contents list, line numbers.
void FindHyperlinksInMarkdown(Line &line, size_t line_index)
Find any hyperlinks in a given line.
High level window description.
Data structure for an opened window.
void ReInit(int rx=0, int ry=0, bool reposition=false)
Re-initialize a window, and optionally change its size.
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window's data as invalid (in need of re-computing)
void SetWidgetDirty(WidgetID widget_index) const
Invalidate a widget, i.e.
ResizeInfo resize
Resize information.
void DisableWidget(WidgetID widget_index)
Sets a widget to disabled.
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
bool IsWidgetLowered(WidgetID widget_index) const
Gets the lowered state of a widget.
void EnableWidget(WidgetID widget_index)
Sets a widget to Enabled.
int top
y position of top edge of the window
int GetRowFromWidget(int clickpos, WidgetID widget, int padding, int line_height=-1) const
Compute the row of a widget that a user clicked in.
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
HyperlinkType
Types of link we support in markdown files.
@ File
Link to a local file.
@ Internal
Internal link, or "anchor" in HTML language.
@ Web
Link to an external website.
static constexpr NWidgetPart _nested_textfile_widgets[]
Widgets for the textfile window.
static std::vector< char > Gunzip(std::span< char > input)
Do an in-memory gunzip operation.
static std::vector< char > Xunzip(std::span< char > input)
Do an in-memory xunzip operation.
static WindowDesc _textfile_desc(WDP_CENTER, "textfile", 630, 460, WC_TEXTFILE, WC_NONE, {}, _nested_textfile_widgets)
Window definition for the textfile window.
static HyperlinkType ClassifyHyperlink(const std::string &destination, bool trusted)
Classify the type of hyperlink the destination describes.
static const std::regex _markdown_link_regex
Regular expression that searches for Markdown links.
static std::string MakeAnchorSlug(const std::string &line)
Create a valid slug for the anchor.
std::optional< std::string > GetTextfile(TextfileType type, Subdirectory dir, std::string_view filename)
Search a textfile file next to the given content.
GUI functions related to textfiles.
TextfileType
Additional text files accompanying Tar archives.
@ WDP_CENTER
Center the window.
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
@ WC_TEXTFILE
textfile; Window numbers: