OpenTTD Source 20250524-master-gc366e6a48e
textfile_gui.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "stdafx.h"
11#include "core/backup_type.hpp"
12#include "fileio_func.h"
13#include "fontcache.h"
14#include "gfx_type.h"
15#include "gfx_func.h"
16#include "string_func.h"
18#include "textfile_gui.h"
19#include "dropdown_type.h"
20#include "dropdown_func.h"
21#include "gfx_layout.h"
22#include "debug.h"
23#include "openttd.h"
24
25#include "widgets/misc_widget.h"
26
27#include "table/strings.h"
28#include "table/control_codes.h"
29
30#if defined(WITH_ZLIB)
31#include <zlib.h>
32#endif
33
34#if defined(WITH_LIBLZMA)
35#include <lzma.h>
36#endif
37
38#include <regex>
39
40#include "safeguards.h"
41
45 NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
46 NWidget(WWT_PUSHARROWBTN, COLOUR_MAUVE, WID_TF_NAVBACK), SetFill(0, 1), SetMinimalSize(15, 1), SetArrowWidgetTypeTip(AWV_DECREASE, STR_TEXTFILE_NAVBACK_TOOLTIP),
47 NWidget(WWT_PUSHARROWBTN, COLOUR_MAUVE, WID_TF_NAVFORWARD), SetFill(0, 1), SetMinimalSize(15, 1), SetArrowWidgetTypeTip(AWV_INCREASE, STR_TEXTFILE_NAVFORWARD_TOOLTIP),
48 NWidget(WWT_CAPTION, COLOUR_MAUVE, WID_TF_CAPTION), SetToolTip(STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
49 NWidget(WWT_TEXTBTN, COLOUR_MAUVE, WID_TF_WRAPTEXT), SetStringTip(STR_TEXTFILE_WRAP_TEXT, STR_TEXTFILE_WRAP_TEXT_TOOLTIP),
50 NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
53 NWidget(WWT_PANEL, COLOUR_MAUVE),
55 /* As this widget can be toggled, it needs to be a multiplier of FS_MONO. So add a spacer that ensures this. */
58 NWidget(NWID_SPACER), SetFill(1, 1), SetResize(1, 0),
59 NWidget(WWT_DROPDOWN, COLOUR_MAUVE, WID_TF_JUMPLIST), SetStringTip(STR_TEXTFILE_JUMPLIST, STR_TEXTFILE_JUMPLIST_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
60 NWidget(NWID_SPACER), SetFill(1, 1), SetResize(1, 0),
74 NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
76};
77
80 WDP_CENTER, "textfile", 630, 460,
82 {},
84);
85
86TextfileWindow::TextfileWindow(Window *parent, TextfileType file_type) : Window(_textfile_desc), file_type(file_type)
87{
88 /* Init of nested tree is deferred.
89 * TextfileWindow::ConstructWindow must be called by the inheriting window. */
90 this->parent = parent;
91}
92
93void TextfileWindow::ConstructWindow()
94{
95 this->CreateNestedTree();
98 this->GetWidget<NWidgetCore>(WID_TF_CAPTION)->SetStringTip(STR_TEXTFILE_README_CAPTION + this->file_type, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
99 this->GetWidget<NWidgetStacked>(WID_TF_SEL_JUMPLIST)->SetDisplayedPlane(SZSP_HORIZONTAL);
100 this->FinishInitNested(this->file_type);
101
104 this->hscroll->SetStepSize(10); // Speed up horizontal scrollbar
105}
106
111{
112 /* Minimum number of lines that will be flowed. */
113 if (this->num_lines == 0) this->num_lines = std::size(this->lines);
114
115 auto it = this->GetIteratorFromPosition(this->vscroll->GetPosition());
116
117 auto adapter = AlternatingView{this->lines, it};
118 this->reflow_iter = adapter.begin();
119 this->reflow_end = adapter.end();
120}
121
122/* virtual */ void TextfileWindow::UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize)
123{
124 switch (widget) {
126 resize.width = GetCharacterHeight(FS_MONO); // Width is not available here as the font may not be loaded yet.
128
129 size.height = 4 * resize.height + WidgetDimensions::scaled.frametext.Vertical(); // At least 4 lines are visible.
130 size.width = std::max(200u, size.width); // At least 200 pixels wide.
131 break;
132 }
133}
134
137{
138 this->vscroll->SetCount(this->num_lines);
139 this->hscroll->SetCount(this->IsTextWrapped() ? 0 : CeilDiv(this->max_width, this->resize.step_width));
140
143 this->SetWidgetDisabledState(WID_TF_HSCROLLBAR, this->IsTextWrapped());
144}
145
146
148static const std::regex _markdown_link_regex{"\\[(.+?)\\]\\((.+?)\\)", std::regex_constants::ECMAScript | std::regex_constants::optimize};
149
151enum class HyperlinkType : uint8_t {
152 Internal,
153 Web,
154 File,
155 Unknown,
156};
157
165static HyperlinkType ClassifyHyperlink(const std::string &destination, bool trusted)
166{
167 if (destination.empty()) return HyperlinkType::Unknown;
168 if (destination.starts_with("#")) return HyperlinkType::Internal;
169
170 /* Only allow external / internal links for sources we trust. */
171 if (!trusted) return HyperlinkType::Unknown;
172
173 if (destination.starts_with("http://")) return HyperlinkType::Web;
174 if (destination.starts_with("https://")) return HyperlinkType::Web;
175 if (destination.starts_with("./")) return HyperlinkType::File;
177}
178
185static std::string MakeAnchorSlug(const std::string &line)
186{
187 std::string r = "#";
188 uint state = 0;
189 for (char c : line) {
190 if (state == 0) {
191 /* State 0: Skip leading hashmarks and spaces. */
192 if (c == '#') continue;
193 if (c == ' ') continue;
194 state = 1;
195 }
196 if (state == 2) {
197 /* State 2: Wait for a non-space/dash character.
198 * When found, output a dash and that character. */
199 if (c == ' ' || c == '-') continue;
200 r += '-';
201 state = 1;
202 }
203 if (state == 1) {
204 /* State 1: Normal text.
205 * Lowercase alphanumerics,
206 * spaces and dashes become dashes,
207 * everything else is removed. */
208 if (isalnum(c)) {
209 r += tolower(c);
210 } else if (c == ' ' || c == '-') {
211 state = 2;
212 }
213 }
214 }
215
216 return r;
217}
218
225void TextfileWindow::FindHyperlinksInMarkdown(Line &line, size_t line_index)
226{
227 std::string::const_iterator last_match_end = line.text.cbegin();
228 std::string fixed_line;
229 StringBuilder builder(fixed_line);
230
231 std::sregex_iterator matcher{ line.text.cbegin(), line.text.cend(), _markdown_link_regex};
232 while (matcher != std::sregex_iterator()) {
233 std::smatch match = *matcher;
234
235 Hyperlink &link = this->links.emplace_back(line_index, 0, 0, match[2].str());
236
237 HyperlinkType link_type = ClassifyHyperlink(link.destination, this->trusted);
238 StringControlCode link_colour;
239 switch (link_type) {
241 link_colour = SCC_GREEN;
242 break;
244 link_colour = SCC_LTBLUE;
245 break;
247 link_colour = SCC_LTBROWN;
248 break;
249 default:
250 /* Don't make other link types fancy as they aren't handled (yet). */
251 link_colour = SCC_CONTROL_END;
252 break;
253 }
254
255 if (link_colour != SCC_CONTROL_END) {
256 /* Format the link to look like a link. */
257 builder += std::string_view(last_match_end, match[0].first);
258 link.begin = fixed_line.length();
259 builder.PutUtf8(SCC_PUSH_COLOUR);
260 builder.PutUtf8(link_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;
265 }
266
267 /* Find next link. */
268 ++matcher;
269 }
270 if (last_match_end == line.text.cbegin()) return; // nothing found
271
272 /* Add remaining text on line. */
273 fixed_line += std::string(last_match_end, line.text.cend());
274
275 /* Overwrite original line text with "fixed" line text. */
276 line.text = std::move(fixed_line);
277}
278
285{
286 if (this->links.empty()) return nullptr;
287
288 /* Which line was clicked. */
290
291 int visible_line = 0;
292 auto it = std::ranges::find_if(this->lines, [&visible_line, clicked_row](const Line &l) {
293 visible_line += l.num_lines;
294 return (visible_line - l.num_lines) <= clicked_row && visible_line > clicked_row;
295 });
296 if (it == this->lines.cend()) return nullptr;
297
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);
301
302 /* Find hyperlinks in this line. */
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);
306 }
307 if (found_links.empty()) return nullptr;
308
309 /* Build line layout to figure out character position that was clicked. */
310 const Line &line = this->lines[line_index];
311 Layouter layout(line.text, line.wrapped_width, FS_MONO);
312 assert(subline < layout.size());
313 ptrdiff_t char_index = layout.GetCharAtPosition(pt.x - WidgetDimensions::scaled.frametext.left, subline);
314 if (char_index < 0) return nullptr;
315 Debug(misc, 4, "TextfileWindow check hyperlink click: line={}, subline={}, char_index={}", line_index, subline, char_index);
316
317 /* Found character index in line, check if any links are at that position. */
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);
322 return link;
323 }
324 }
325
326 return nullptr;
327}
328
334void TextfileWindow::AppendHistory(const std::string &filepath)
335{
336 this->history.erase(this->history.begin() + this->history_pos + 1, this->history.end());
338 this->history.emplace_back(filepath, 0);
341 this->history_pos = this->history.size() - 1;
342}
343
351
358{
359 if (delta == 0) return;
360 if (delta < 0 && static_cast<int>(this->history_pos) < -delta) return;
361 if (delta > 0 && this->history_pos + delta >= this->history.size()) return;
362
364 this->history_pos += delta;
365
366 if (this->history[this->history_pos].filepath != this->filepath) {
367 this->filepath = this->history[this->history_pos].filepath;
368 this->filename = this->filepath.substr(this->filepath.find_last_of(PATHSEP) + 1);
369 this->LoadTextfile(this->filepath, NO_DIRECTORY);
370 }
371
372 this->SetWidgetDisabledState(WID_TF_NAVFORWARD, this->history_pos + 1 >= this->history.size());
374 this->GetScrollbar(WID_TF_VSCROLLBAR)->SetPosition(this->history[this->history_pos].scrollpos);
376 this->SetDirty();
377}
378
379/* virtual */ void TextfileWindow::OnHyperlinkClick(const Hyperlink &link)
380{
381 switch (ClassifyHyperlink(link.destination, this->trusted)) {
383 {
384 auto it = std::ranges::find(this->link_anchors, link.destination, &Hyperlink::destination);
385 if (it != this->link_anchors.cend()) {
386 this->AppendHistory(this->filepath);
387 this->ScrollToLine(it->line);
389 }
390 break;
391 }
392
394 OpenBrowser(link.destination);
395 break;
396
398 this->NavigateToFile(link.destination, 0);
399 break;
400
401 default:
402 /* Do nothing */
403 break;
404 }
405}
406
413void TextfileWindow::NavigateToFile(std::string newfile, size_t line)
414{
415 /* Double-check that the file link begins with ./ as a relative path. */
416 if (!newfile.starts_with("./")) return;
417
418 /* Get the path portion of the current file path. */
419 std::string newpath = this->filepath;
420 size_t pos = newpath.find_last_of(PATHSEPCHAR);
421 if (pos == std::string::npos) {
422 newpath.clear();
423 } else {
424 newpath.erase(pos + 1);
425 }
426
427 /* Check and remove for anchor in link. Do this before we find the filename, as people might have a / after the hash. */
428 size_t anchor_pos = newfile.find_first_of('#');
429 std::string anchor;
430 if (anchor_pos != std::string::npos) {
431 anchor = newfile.substr(anchor_pos);
432 newfile.erase(anchor_pos);
433 }
434
435 /* Now the anchor is gone, check if this is a markdown or textfile. */
436 if (!StrEndsWithIgnoreCase(newfile, ".md") && !StrEndsWithIgnoreCase(newfile, ".txt")) return;
437
438 /* Convert link destination to acceptable local filename (replace forward slashes with correct path separator). */
439 newfile = newfile.substr(2);
440 if (PATHSEPCHAR != '/') {
441 for (char &c : newfile) {
442 if (c == '/') c = PATHSEPCHAR;
443 }
444 }
445
446 /* Paste the two together and check file exists. */
447 newpath = newpath + newfile;
448 if (!FioCheckFileExists(newpath, NO_DIRECTORY)) return;
449
450 /* Update history. */
451 this->AppendHistory(newpath);
452
453 /* Load the new file. */
454 this->filepath = newpath;
455 this->filename = newpath.substr(newpath.find_last_of(PATHSEP) + 1);
456
457 this->LoadTextfile(this->filepath, NO_DIRECTORY);
458
461
462 if (anchor.empty() || line != 0) {
463 this->ScrollToLine(line);
464 } else {
465 auto anchor_dest = std::ranges::find(this->link_anchors, anchor, &Hyperlink::destination);
466 if (anchor_dest != this->link_anchors.cend()) {
467 this->ScrollToLine(anchor_dest->line);
469 } else {
470 this->ScrollToLine(0);
471 }
472 }
473}
474
476{
477 this->link_anchors.clear();
478
479 if (StrEndsWithIgnoreCase(this->filename, ".md")) this->AfterLoadMarkdown();
480
481 if (this->GetWidget<NWidgetStacked>(WID_TF_SEL_JUMPLIST)->SetDisplayedPlane(this->jumplist.empty() ? SZSP_HORIZONTAL : 0)) this->ReInit();
482}
483
488{
489 for (size_t line_index = 0; line_index < this->lines.size(); ++line_index) {
490 Line &line = this->lines[line_index];
491
492 /* Find and mark all hyperlinks in the line. */
493 this->FindHyperlinksInMarkdown(line, line_index);
494
495 /* All lines beginning with # are headings. */
496 if (!line.text.empty() && line.text[0] == '#') {
497 this->jumplist.push_back(line_index);
498 this->lines[line_index].colour = TC_GOLD;
499 this->link_anchors.emplace_back(line_index, 0, 0, MakeAnchorSlug(line.text));
500 }
501 }
502}
503
504/* virtual */ void TextfileWindow::OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count)
505{
506 switch (widget) {
507 case WID_TF_WRAPTEXT:
509 this->InvalidateData();
510 break;
511
512 case WID_TF_JUMPLIST: {
513 DropDownList list;
514 for (size_t line : this->jumplist) {
515 list.push_back(MakeDropDownListStringItem(GetString(STR_TEXTFILE_JUMPLIST_ITEM, this->lines[line].text), (int)line));
516 }
517 ShowDropDownList(this, std::move(list), -1, widget);
518 break;
519 }
520
521 case WID_TF_NAVBACK:
522 this->NavigateHistory(-1);
523 break;
524
526 this->NavigateHistory(+1);
527 break;
528
529 case WID_TF_BACKGROUND: {
530 const Hyperlink *link = this->GetHyperlink(pt);
531 if (link != nullptr) this->OnHyperlinkClick(*link);
532 break;
533 }
534 }
535}
536
537/* virtual */ bool TextfileWindow::OnTooltip([[maybe_unused]] Point pt, WidgetID widget, TooltipCloseCondition close_cond)
538{
539 if (widget != WID_TF_BACKGROUND) return false;
540
541 const Hyperlink *link = this->GetHyperlink(pt);
542 if (link == nullptr) return false;
543
544 GuiShowTooltips(this, GetEncodedString(STR_JUST_RAW_STRING, link->destination), close_cond);
545
546 return true;
547}
548
549/* virtual */ void TextfileWindow::DrawWidget(const Rect &r, WidgetID widget) const
550{
551 if (widget == WID_TF_CAPTION && std::size(this->lines) > 0 && this->reflow_iter != this->reflow_end) {
552 /* Draw a progress bar in the caption. */
553 Rect fr = r.Shrink(WidgetDimensions::scaled.captiontext).WithHeight(WidgetDimensions::scaled.vsep_normal, true);
554 size_t remaining = std::distance(this->reflow_iter, this->reflow_end);
555 fr = fr.WithWidth(static_cast<int>(remaining * fr.Width() / std::size(this->lines)), _current_text_dir != TD_RTL);
557 }
558
559 if (widget != WID_TF_BACKGROUND) return;
560
561 Rect fr = r.Shrink(WidgetDimensions::scaled.frametext);
562
563 DrawPixelInfo new_dpi;
564 if (!FillDrawPixelInfo(&new_dpi, fr)) return;
565 AutoRestoreBackup dpi_backup(_cur_dpi, &new_dpi);
566
567 /* Draw content (now coordinates given to DrawString* are local to the new clipping region). */
568 fr = fr.Translate(-fr.left, -fr.top);
569 int line_height = GetCharacterHeight(FS_MONO);
570
571 if (!this->IsTextWrapped()) fr = ScrollRect(fr, *this->hscroll, this->resize.step_width);
572
573 int pos = this->vscroll->GetPosition();
574 int cap = this->vscroll->GetCapacity();
575 int cur_line = 0;
576 for (auto &line : this->lines) {
577 int top = cur_line;
578 cur_line += line.num_lines;
579 if (cur_line <= pos) continue;
580 if (top > pos + cap) break;
581
582 int y_offset = (top - pos) * line_height;
583 if (line.wrapped_width != 0) {
584 Rect tr = fr.WithWidth(line.wrapped_width, _current_text_dir == TD_RTL);
585 DrawStringMultiLineWithClipping(tr.left, tr.right, y_offset, y_offset + line.num_lines * line_height, line.text, line.colour, SA_TOP | SA_LEFT, false, FS_MONO);
586 } else {
587 DrawString(fr.left, fr.right, y_offset, line.text, line.colour, SA_TOP | SA_LEFT, false, FS_MONO);
588 }
589 }
590}
591
592/* virtual */ void TextfileWindow::OnResize()
593{
595 this->hscroll->SetCapacityFromWidget(this, WID_TF_BACKGROUND, WidgetDimensions::scaled.framerect.Horizontal());
596
597 this->UpdateVisibleIterators();
598 this->ReflowContent();
599 this->SetupScrollbars();
600}
601
602/* virtual */ void TextfileWindow::OnInit()
603{
604 /* If font has changed we need to recalculate the maximum width. */
605 this->num_lines = 0;
606 this->max_width = 0;
607 for (auto &line : this->lines) {
608 line.max_width = -1;
609 line.num_lines = 1;
610 line.wrapped_width = 0;
611 }
612
613 this->ReflowContent();
614}
615
616/* virtual */ void TextfileWindow::OnInvalidateData([[maybe_unused]] int data, [[maybe_unused]] bool gui_scope)
617{
618 if (!gui_scope) return;
619
620 this->ReflowContent();
621 this->SetupScrollbars();
622}
623
625{
626 if (widget != WID_TF_JUMPLIST) return;
627
628 this->ScrollToLine(index);
629}
630
631extern bool CanContinueRealtimeTick();
632
633TextfileWindow::ReflowState TextfileWindow::ContinueReflow()
634{
635 if (this->reflow_iter == this->reflow_end) return ReflowState::None;
636
637 int window_width = this->GetWidget<NWidgetCore>(WID_TF_BACKGROUND)->current_x - WidgetDimensions::scaled.frametext.Horizontal();
638
639 bool wrapped = this->IsTextWrapped();
640 bool dirty = false;
641 int pos = this->vscroll->GetPosition();
642
643 for (/* nothing */; this->reflow_iter != this->reflow_end; ++this->reflow_iter) {
644 auto it = this->reflow_iter.Base();
645 Line &line = *it;
646
647 int old_lines = line.num_lines;
648 if (wrapped) {
649 if (line.wrapped_width != window_width) {
650 line.num_lines = GetStringHeight(line.text, window_width, FS_MONO) / GetCharacterHeight(FS_MONO);
651 line.wrapped_width = window_width;
652 }
653 } else {
654 if (line.max_width == -1) {
655 line.max_width = GetStringBoundingBox(line.text, FS_MONO).width;
656 this->max_width = std::max(this->max_width, line.max_width);
657 }
658 line.num_lines = 1;
659 line.wrapped_width = 0;
660 }
661
662 /* Adjust the total number of lines. */
663 this->num_lines += (line.num_lines - old_lines);
664
665 /* Maintain scroll position. */
666 if (this->visible_first > it) pos += (line.num_lines - old_lines);
667
668 /* Mark dirty if visible range is touched. */
669 if (it >= this->visible_first && it <= this->visible_last) dirty = true;
670
671 if (!CanContinueRealtimeTick()) break;
672 }
673
674 if (this->vscroll->SetPosition(pos)) dirty = true;
675
677}
678
680{
681 auto r = this->ContinueReflow();
682 if (r == ReflowState::None) return;
683
684 this->SetupScrollbars();
685
688 this->UpdateVisibleIterators();
689 }
690
691 /* Caption is always dirty. */
693}
694
695void TextfileWindow::UpdateVisibleIterators()
696{
697 int pos = this->vscroll->GetPosition();
698 int cap = this->vscroll->GetCapacity();
699 this->visible_first = this->GetIteratorFromPosition(pos);
700
701 /* The last visible iterator ignores line wrapping so that it does not need to change when line heights change. */
702 this->visible_last = std::ranges::next(this->visible_first, cap + 1, std::end(this->lines));
703}
704
706{
707 if (widget != WID_TF_VSCROLLBAR) return;
708
709 this->UpdateVisibleIterators();
710 this->ReflowContent();
711}
712
713std::vector<TextfileWindow::Line>::iterator TextfileWindow::GetIteratorFromPosition(int pos)
714{
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;
718 }
719 return std::end(this->lines);
720}
721
722void TextfileWindow::ScrollToLine(size_t line)
723{
725 int newpos = 0;
726 for (auto it = std::begin(this->lines); it != std::end(this->lines) && line > 0; --line, ++it) {
727 newpos += it->num_lines;
728 }
729 sb->SetPosition(std::min(newpos, sb->GetCount() - sb->GetCapacity()));
730 this->UpdateVisibleIterators();
731 this->ReflowContent();
732 this->SetDirty();
733}
734
735bool TextfileWindow::IsTextWrapped() const
736{
737 return this->IsWidgetLowered(WID_TF_WRAPTEXT);
738}
739
740/* virtual */ void TextfileWindow::Reset()
741{
742 this->search_iterator = 0;
743}
744
746{
747 return FS_MONO;
748}
749
750/* virtual */ std::optional<std::string_view> TextfileWindow::NextString()
751{
752 if (this->search_iterator >= this->lines.size()) return std::nullopt;
753
754 return this->lines[this->search_iterator++].text;
755}
756
757/* virtual */ bool TextfileWindow::Monospace()
758{
759 return true;
760}
761
762/* virtual */ void TextfileWindow::SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] std::string_view font_name, [[maybe_unused]] const void *os_data)
763{
764#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
765 settings->mono.font = font_name;
766 settings->mono.os_handle = os_data;
767#endif
768}
769
770#if defined(WITH_ZLIB)
771
780static std::vector<char> Gunzip(std::span<char> input)
781{
782 static const int BLOCKSIZE = 8192;
783 std::vector<char> output;
784
785 z_stream z{};
786 z.next_in = reinterpret_cast<Bytef *>(input.data());
787 z.avail_in = static_cast<uInt>(input.size());
788
789 /* window size = 15, add 32 to enable gzip or zlib header processing */
790 int res = inflateInit2(&z, 15 + 32);
791 /* Z_BUF_ERROR just means we need more space */
792 while (res == Z_OK || (res == Z_BUF_ERROR && z.avail_out == 0)) {
793 /* When we get here, we're either just starting, or
794 * inflate is out of output space - allocate more */
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);
799 }
800
801 inflateEnd(&z);
802 if (res != Z_STREAM_END) return {};
803
804 output.resize(output.size() - z.avail_out);
805 return output;
806}
807#endif
808
809#if defined(WITH_LIBLZMA)
810
819static std::vector<char> Xunzip(std::span<char> input)
820{
821 static const int BLOCKSIZE = 8192;
822 std::vector<char> output;
823
824 lzma_stream z = LZMA_STREAM_INIT;
825 z.next_in = reinterpret_cast<uint8_t *>(input.data());
826 z.avail_in = input.size();
827
828 int res = lzma_auto_decoder(&z, UINT64_MAX, LZMA_CONCATENATED);
829 /* Z_BUF_ERROR just means we need more space */
830 while (res == LZMA_OK || (res == LZMA_BUF_ERROR && z.avail_out == 0)) {
831 /* When we get here, we're either just starting, or
832 * inflate is out of output space - allocate more */
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);
837 }
838
839 lzma_end(&z);
840 if (res != LZMA_STREAM_END) return {};
841
842 output.resize(output.size() - z.avail_out);
843 return output;
844}
845#endif
846
847
851/* virtual */ void TextfileWindow::LoadTextfile(const std::string &textfile, Subdirectory dir)
852{
853 this->lines.clear();
854 this->jumplist.clear();
855
856 if (this->GetWidget<NWidgetStacked>(WID_TF_SEL_JUMPLIST)->SetDisplayedPlane(SZSP_HORIZONTAL)) this->ReInit();
857
858 if (textfile.empty()) return;
859
860 /* Get text from file */
861 size_t filesize;
862 auto handle = FioFOpenFile(textfile, "rb", dir, &filesize);
863 if (!handle.has_value()) return;
864 /* Early return on empty files. */
865 if (filesize == 0) return;
866
867 std::vector<char> buf;
868 buf.resize(filesize);
869 size_t read = fread(buf.data(), 1, buf.size(), *handle);
870
871 if (read != buf.size()) return;
872
873#if defined(WITH_ZLIB)
874 /* In-place gunzip */
875 if (textfile.ends_with(".gz")) buf = Gunzip(buf);
876#endif
877
878#if defined(WITH_LIBLZMA)
879 /* In-place xunzip */
880 if (textfile.ends_with(".xz")) buf = Xunzip(buf);
881#endif
882
883 if (buf.empty()) return;
884
885 std::string_view sv_buf(buf.data(), buf.size());
886
887 /* Check for the byte-order-mark, and skip it if needed. */
888 if (sv_buf.starts_with("\ufeff")) sv_buf.remove_prefix(3);
889
890 /* Update the filename. */
891 this->filepath = textfile;
892 this->filename = this->filepath.substr(this->filepath.find_last_of(PATHSEP) + 1);
893 /* If it's the first file being loaded, add to history. */
894 if (this->history.empty()) this->history.emplace_back(this->filepath, 0);
895
896 /* Process the loaded text into lines, and do any further parsing needed. */
897 this->LoadText(sv_buf);
898}
899
907void TextfileWindow::LoadText(std::string_view buf)
908{
910 this->lines.clear();
911
912 /* Split the string on newlines. */
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);
918
919 next = p.find_first_of('\n');
920 }
921 this->lines.emplace_back(p);
922
923 this->AfterLoadText();
924 this->ReflowContent();
925
926 CheckForMissingGlyphs(true, this);
927
928 /* The font may have changed when searching for glyphs, so ensure widget sizes are updated just in case. */
929 this->ReInit();
930}
931
939std::optional<std::string> GetTextfile(TextfileType type, Subdirectory dir, std::string_view filename)
940{
941 static const std::string_view prefixes[] = {
942 "readme",
943 "changelog",
944 "license",
945 };
946 static_assert(lengthof(prefixes) == TFT_CONTENT_END);
947
948 /* Only the generic text file types allowed for this function */
949 if (type >= TFT_CONTENT_END) return std::nullopt;
950
951 std::string_view prefix = prefixes[type];
952
953 if (filename.empty()) return std::nullopt;
954
955 auto slash = filename.find_last_of(PATHSEPCHAR);
956 if (slash == std::string::npos) return std::nullopt;
957
958 std::string_view base_path = filename.substr(0, slash + 1);
959
960 static const std::initializer_list<const std::string_view> extensions{
961 "txt",
962 "md",
963#if defined(WITH_ZLIB)
964 "txt.gz",
965 "md.gz",
966#endif
967#if defined(WITH_LIBLZMA)
968 "txt.xz",
969 "md.xz",
970#endif
971 };
972
973 for (auto &extension : extensions) {
974 std::string file_path = fmt::format("{}{}_{}.{}", base_path, prefix, GetCurrentLanguageIsoCode(), extension);
975 if (FioCheckFileExists(file_path, dir)) return file_path;
976
977 file_path = fmt::format("{}{}_{:.2s}.{}", base_path, prefix, GetCurrentLanguageIsoCode(), extension);
978 if (FioCheckFileExists(file_path, dir)) return file_path;
979
980 file_path = fmt::format("{}{}.{}", base_path, prefix, extension);
981 if (FioCheckFileExists(file_path, dir)) return file_path;
982 }
983 return std::nullopt;
984}
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.
Definition gfx_layout.h:160
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.
Scrollbar data structure.
size_type GetCapacity() const
Gets the number of visible elements of the scrollbar.
void SetCount(size_t num)
Sets the number of elements in the list.
bool SetPosition(size_type position)
Sets the position of the first visible element.
void SetCapacityFromWidget(Window *w, WidgetID widget, int padding=0)
Set capacity of visible elements from the size and resize properties of a widget.
Definition widget.cpp:2556
size_type GetCount() const
Gets the number of elements in the list.
void SetStepSize(size_t stepsize)
Set the distance to scroll when using the buttons or the wheel.
size_type GetPosition() const
Gets the position of the first visible element in the list.
Compose data into a growing std::string.
RectPadding frametext
Padding inside frame with text.
Definition window_gui.h:41
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:30
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition window_gui.h:93
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.
Definition debug.h:37
void ShowDropDownList(Window *w, DropDownList &&list, int selected, WidgetID button, uint width, bool instant_close, bool persist)
Show a drop down list.
Definition dropdown.cpp:408
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.
Definition fileio.cpp:242
bool FioCheckFileExists(std::string_view filename, Subdirectory subdir)
Check whether the given file exists.
Definition fileio.cpp:121
Functions for Standard In/Out file operations.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:87
@ 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.
Definition fontcache.cpp:77
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).
Definition gfx.cpp:705
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:887
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.
Definition gfx.cpp:658
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.
Definition gfx.cpp:115
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...
Definition gfx.cpp:860
bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
Set up a clipping area for only drawing into a certain area.
Definition gfx.cpp:1554
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.
Definition gfx_type.h:250
@ FS_MONO
Index of the monospaced font in the font tables.
Definition gfx_type.h:254
@ SA_TOP
Top align the text.
Definition gfx_type.h:388
@ SA_LEFT
Left align the text.
Definition gfx_type.h:383
@ FILLRECT_CHECKER
Draw only every second pixel, used for greying-out.
Definition gfx_type.h:341
constexpr NWidgetPart SetFill(uint16_t fill_x, uint16_t fill_y)
Widget part function for setting filling.
constexpr NWidgetPart SetPIP(uint8_t pre, uint8_t inter, uint8_t post)
Widget part function for setting a pre/inter/post spaces.
constexpr NWidgetPart SetScrollbar(WidgetID index)
Attach a scrollbar to a widget.
constexpr NWidgetPart SetStringTip(StringID string, StringID tip={})
Widget part function for setting the string and tooltip.
constexpr NWidgetPart SetMinimalSize(int16_t x, int16_t y)
Widget part function for setting the minimal size.
constexpr NWidgetPart NWidget(WidgetType tp, Colours col, WidgetID idx=-1)
Widget part function for starting a new 'real' widget.
constexpr NWidgetPart SetToolTip(StringID tip)
Widget part function for setting tooltip and clearing the widget data.
constexpr NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
constexpr NWidgetPart SetArrowWidgetTypeTip(ArrowWidgetValues widget_type, StringID tip={})
Widget part function for setting the arrow widget type and tooltip.
constexpr NWidgetPart SetMinimalTextLines(uint8_t lines, uint8_t spacing, FontSize size=FS_NORMAL)
Widget part function for setting the minimal text lines.
constexpr NWidgetPart SetResize(int16_t dx, int16_t dy)
Widget part function for setting the resize step.
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
Definition window.cpp:955
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.
Definition misc_gui.cpp:688
Types related to the misc widgets.
@ WID_TF_JUMPLIST
List to jump around the file.
Definition misc_widget.h:54
@ WID_TF_BACKGROUND
Panel to draw the textfile on.
Definition misc_widget.h:56
@ WID_TF_NAVBACK
Navigate back button.
Definition misc_widget.h:51
@ WID_TF_WRAPTEXT
Whether or not to wrap the text.
Definition misc_widget.h:53
@ WID_TF_NAVFORWARD
Navigate forward button.
Definition misc_widget.h:52
@ WID_TF_CAPTION
The caption of the window.
Definition misc_widget.h:50
@ WID_TF_SEL_JUMPLIST
Selection to display the jump list or not.
Definition misc_widget.h:55
@ WID_TF_HSCROLLBAR
Horizontal scrollbar to scroll through the textfile left-to-right.
Definition misc_widget.h:58
@ WID_TF_VSCROLLBAR
Vertical scrollbar to scroll through the textfile up-and-down.
Definition misc_widget.h:57
Some generic types.
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.
Definition stdafx.h:271
bool StrEndsWithIgnoreCase(std::string_view str, std::string_view suffix)
Check whether the given string ends with the given suffix, ignoring case.
Definition string.cpp:295
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:117
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 ...
Definition strings.cpp:2353
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:91
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:415
std::string_view GetCurrentLanguageIsoCode()
Get the ISO language code of the currently loaded language.
Definition strings.cpp:2249
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:57
@ 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.
Definition gfx_type.h:158
Settings for the four different fonts.
Definition fontcache.h:200
Partial widget specification to allow NWidgets to be written nested.
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.
Definition window_gui.h:211
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.
Definition window_gui.h:167
Data structure for an opened window.
Definition window_gui.h:273
void ReInit(int rx=0, int ry=0, bool reposition=false)
Re-initialize a window, and optionally change its size.
Definition window.cpp:967
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
Definition window.cpp:1778
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window's data as invalid (in need of re-computing)
Definition window.cpp:3205
void SetWidgetDirty(WidgetID widget_index) const
Invalidate a widget, i.e.
Definition window.cpp:555
ResizeInfo resize
Resize information.
Definition window_gui.h:314
void DisableWidget(WidgetID widget_index)
Sets a widget to disabled.
Definition window_gui.h:391
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
Definition window.cpp:1768
bool IsWidgetLowered(WidgetID widget_index) const
Gets the lowered state of a widget.
Definition window_gui.h:491
void EnableWidget(WidgetID widget_index)
Sets a widget to Enabled.
Definition window_gui.h:400
int top
y position of top edge of the window
Definition window_gui.h:310
int GetRowFromWidget(int clickpos, WidgetID widget, int padding, int line_height=-1) const
Compute the row of a widget that a user clicked in.
Definition window.cpp:211
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:312
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:381
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
Definition window_gui.h:450
HyperlinkType
Types of link we support in markdown files.
@ File
Link to a local file.
@ Unknown
Unknown link.
@ 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.
Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
Apply 'scroll' to a rect to be drawn in.
Definition widget.cpp:2573
@ WWT_PUSHARROWBTN
Normal push-button (no toggle button) with arrow caption.
@ NWID_SPACER
Invisible widget that takes some space.
Definition widget_type.h:71
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:67
@ WWT_TEXTBTN
(Toggle) Button with text
Definition widget_type.h:45
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:40
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:53
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:77
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:69
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:61
@ NWID_HSCROLLBAR
Horizontal scrollbar.
Definition widget_type.h:76
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:60
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX)
Definition widget_type.h:57
@ WWT_DROPDOWN
Drop down list.
Definition widget_type.h:62
@ NWID_SELECTION
Stacked widgets, only one visible at a time (eg in a panel with tabs).
Definition widget_type.h:72
@ SZSP_HORIZONTAL
Display plane with zero size vertically, and filling and resizing horizontally.
@ AWV_DECREASE
Arrow to the left or in case of RTL to the right.
Definition widget_type.h:21
@ AWV_INCREASE
Arrow to the right or in case of RTL to the left.
Definition widget_type.h:22
@ WDP_CENTER
Center the window.
Definition window_gui.h:145
int WidgetID
Widget ID.
Definition window_type.h:20
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
Definition window_type.h:47
@ WC_TEXTFILE
textfile; Window numbers: