Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
36 KB
Referenced Files
None
Subscribers
None
diff --git a/src/HelpManager.cpp b/src/HelpManager.cpp
index 4f59b27..1d292b1 100644
--- a/src/HelpManager.cpp
+++ b/src/HelpManager.cpp
@@ -1,1184 +1,1184 @@
/*
* Copyright (C) 2019 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "HelpManager.h"
#include "Functions.h"
#include "GUIWindow.h"
#include "GUIListBox.h"
#include "GUITextArea.h"
#include "WordWrapper.h"
#include "ThemeManager.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <stdio.h>
#include "SDL_ttf_fontfallback.h"
class Chunk {
public:
int cachedMaxWidth, cachedWidth, cachedNumberOfLines;
public:
Chunk() : cachedMaxWidth(-1), cachedWidth(-1), cachedNumberOfLines(-1) {}
virtual ~Chunk() {}
void updateSize(int maxWidth) {
if (maxWidth != cachedMaxWidth) updateSizeForced(maxWidth);
}
virtual void updateSizeForced(int maxWidth) = 0;
virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) = 0;
};
class ParagraphChunk : public Chunk {
public:
std::string text;
std::vector<std::string> cachedLines;
virtual void updateSizeForced(int maxWidth) override {
WordWrapper wrapper;
wrapper.font = fontText;
wrapper.wordWrap = true;
wrapper.maxWidth = maxWidth;
wrapper.hyphen = "-";
wrapper.hyphenatorLanguage = "en";
wrapper.reservedFragments.insert("/");
//TODO: verbatim support
cachedLines.clear();
cachedMaxWidth = maxWidth;
cachedWidth = wrapper.addString(cachedLines, text);
cachedNumberOfLines = cachedLines.size();
}
virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) override {
SDL_Color fg = objThemes.getTextColor(true);
for (const std::string& s : cachedLines) {
surfaces.emplace_back(TTF_RenderUTF8_Blended(fontText, s.c_str(), fg));
}
//TODO: extract hyperlinks
}
};
struct FakeTOCItem {
std::string caption;
int level, pageIndex;
};
class FakeTOCChunk : public Chunk {
public:
std::vector<FakeTOCItem> items;
virtual void updateSizeForced(int maxWidth) override {
int width = 0;
for (const FakeTOCItem& item : items) {
int w = 0;
TTF_SizeUTF8(fontText, item.caption.c_str(), &w, NULL);
w += (item.level + 1) * 16;
width = std::max(width, w);
}
cachedMaxWidth = maxWidth;
cachedWidth = width;
cachedNumberOfLines = items.size();
}
virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) override {
SDL_Color fg = objThemes.getTextColor(true);
auto bmGUI = getImageManager().loadImage(getDataPath() + "gfx/gui.png");
for (const FakeTOCItem& item : items) {
SurfacePtr surf1(TTF_RenderUTF8_Blended(fontText, item.caption.empty() ? " " : item.caption.c_str(), fg));
SurfacePtr surf = createSurface(surf1->w + (item.level + 1) * 16, surf1->h);
SDL_Rect srcrect = { 0, 0, surf1->w, surf1->h };
SDL_Rect dstrect = { (item.level + 1) * 16, 0, surf1->w, surf1->h };
SDL_BlitSurface(surf1.get(), &srcrect, surf.get(), &dstrect);
srcrect = SDL_Rect{ 80, 64, 16, 16 };
dstrect = SDL_Rect{ item.level * 16, (surf1->h - 16) / 2, 16, 16 };
SDL_BlitSurface(bmGUI, &srcrect, surf.get(), &dstrect);
if (item.pageIndex >= 0) {
char s[32];
sprintf(s, "page:%d", item.pageIndex);
- links.push_back(GUITextArea::Hyperlink2{ surfaces.size(), (item.level + 1) * 16, surf->w, s });
+ links.push_back(GUITextArea::Hyperlink2{ (int)surfaces.size(), (item.level + 1) * 16, surf->w, s });
}
surfaces.push_back(std::move(surf));
}
}
};
class ItemizeChunk : public Chunk {
public:
std::vector<Chunk*> items;
virtual ~ItemizeChunk() {
for (auto item : items) {
delete item;
}
}
virtual void updateSizeForced(int maxWidth) override {
cachedMaxWidth = maxWidth;
cachedWidth = 0;
cachedNumberOfLines = 0;
for (auto item : items) {
if (item) {
item->updateSize(maxWidth - 16);
cachedWidth = std::max(item->cachedWidth, cachedWidth);
cachedNumberOfLines += item->cachedNumberOfLines;
}
}
cachedWidth += 16;
}
virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) override {
auto bmGUI = getImageManager().loadImage(getDataPath() + "gfx/gui.png");
for (auto item : items) {
if (item) {
std::vector<SurfacePtr> tempSurfaces;
const size_t oldLineCount = surfaces.size();
const size_t oldLinkSize = links.size();
item->createSurfaces(tempSurfaces, links);
for (int i = 0, m = tempSurfaces.size(); i < m; i++) {
SDL_Surface *src = tempSurfaces[i].get();
SurfacePtr surface = createSurface(src->w + 16, src->h);
SDL_Rect srcrect = { 0, 0, src->w, src->h };
SDL_Rect dstrect = { 16, 0, src->w, src->h };
SDL_BlitSurface(src, &srcrect, surface.get(), &dstrect);
if (i == 0) {
srcrect = SDL_Rect{ 80, 64, 16, 16 };
dstrect = SDL_Rect{ 0, (src->h - 16) / 2, 16, 16 };
SDL_BlitSurface(bmGUI, &srcrect, surface.get(), &dstrect);
}
surfaces.push_back(std::move(surface));
}
for (size_t i = oldLinkSize; i < links.size(); i++) {
GUITextArea::Hyperlink2 &link = links[i];
link.line += oldLineCount;
link.startX += 16;
link.endX += 16;
}
}
}
}
};
class TableChunk : public Chunk {
public:
int columns, rows;
std::vector<Chunk*> items;
std::vector<int> cachedRowWidth;
static const int lineWidth = 16;
TableChunk() : columns(0), rows(0) {}
virtual ~TableChunk() {
for (auto item : items) {
delete item;
}
}
virtual void updateSizeForced(int maxWidth) override {
cachedMaxWidth = maxWidth;
cachedWidth = 0;
cachedNumberOfLines = 0;
cachedRowWidth.clear();
for (int i = 0; i < columns; i++) {
int mw = (i < columns - 1) ? 0x40000000 : (maxWidth - lineWidth * 2 - cachedWidth);
int w = 0;
for (int j = 0; j < rows; j++) {
auto item = items[j * columns + i];
if (item) {
item->updateSize(mw);
w = std::max(item->cachedWidth, w);
}
}
cachedRowWidth.push_back(w);
cachedWidth += w + lineWidth;
}
cachedWidth += lineWidth;
for (int j = 0; j < rows; j++) {
int h = 0;
for (int i = 0; i < columns; i++) {
auto item = items[j * columns + i];
if (item) {
h = std::max(item->cachedNumberOfLines, h);
}
}
cachedNumberOfLines += h;
}
cachedNumberOfLines += 3;
}
SurfacePtr createSurfaceWithGridLine(int height, bool drawHorizontal, int verticalTop, int verticalBottom) {
SurfacePtr surface = createSurface(cachedWidth, height);
SDL_Color fg = objThemes.getTextColor(true);
Uint32 color = SDL_MapRGBA(surface->format, fg.r, fg.g, fg.b, 255);
if (drawHorizontal) {
SDL_Rect r = { lineWidth / 2, height / 2, cachedWidth - lineWidth, 1 };
SDL_FillRect(surface.get(), &r, color);
}
if (verticalBottom > verticalTop) {
SDL_Rect r = { lineWidth / 2, verticalTop, 1, verticalBottom - verticalTop };
SDL_FillRect(surface.get(), &r, color);
for (int w : cachedRowWidth) {
r.x += w + lineWidth;
SDL_FillRect(surface.get(), &r, color);
}
}
return surface;
}
virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) override {
int height = TTF_FontHeight(fontText) + 1;
for (int j = 0; j < rows; j++) {
if (j == 0) surfaces.push_back(createSurfaceWithGridLine(height, true, height / 2, height));
const size_t oldLineCount = surfaces.size();
std::vector<std::vector<SurfacePtr> > tempSurfaces(columns);
int h = 0;
int x = lineWidth;
for (int i = 0; i < columns; i++) {
auto item = items[j * columns + i];
if (item) {
const size_t oldLinkSize = links.size();
item->createSurfaces(tempSurfaces[i], links);
h = std::max<int>(tempSurfaces[i].size(), h);
for (size_t k = oldLinkSize; k < links.size(); k++) {
GUITextArea::Hyperlink2 &link = links[k];
link.line += oldLineCount;
link.startX += x;
link.endX += x;
}
}
x += cachedRowWidth[i] + lineWidth;
}
for (int y = 0; y < h; y++) {
SurfacePtr surface = createSurfaceWithGridLine(height, false, 0, height);
int x = lineWidth;
for (int i = 0; i < columns; i++) {
if (y < (int)tempSurfaces[i].size()) {
auto src = tempSurfaces[i][y].get();
SDL_Rect srcrect = { 0, 0, src->w, src->h };
SDL_Rect dstrect = { x, 0, src->w, src->h };
SDL_BlitSurface(src, &srcrect, surface.get(), &dstrect);
}
x += cachedRowWidth[i] + lineWidth;
}
surfaces.push_back(std::move(surface));
}
if (j == 0) surfaces.push_back(createSurfaceWithGridLine(height, true, 0, height));
}
surfaces.push_back(createSurfaceWithGridLine(height, true, 0, height / 2));
}
};
class CodeChunk : public Chunk {
public:
std::vector<std::string> lines;
virtual void updateSizeForced(int maxWidth) override {
int width = 0;
for (const std::string& s : lines) {
int w = 0;
TTF_SizeUTF8(fontMono, s.c_str(), &w, NULL);
width = std::max(width, w);
}
cachedMaxWidth = maxWidth;
cachedWidth = width;
cachedNumberOfLines = lines.size() + 1;
}
virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) override {
SDL_Color fg = objThemes.getTextColor(true);
surfaces.emplace_back(TTF_RenderUTF8_Blended(fontText, "Copy code", fg));
- links.push_back(GUITextArea::Hyperlink2{ surfaces.size() - 1, 0, surfaces.back()->w, "code:" });
+ links.push_back(GUITextArea::Hyperlink2{ (int)surfaces.size() - 1, 0, surfaces.back()->w, "code:" });
for (const std::string& s : lines) {
surfaces.emplace_back(TTF_RenderUTF8_Blended(fontMono, s.c_str(), fg));
links.back().url += s + "\n";
}
}
};
class HeaderChunk : public Chunk {
public:
int level;
Chunk *child;
HeaderChunk() : level(0), child(NULL) {}
virtual ~HeaderChunk() {
delete child;
}
int getAdditionalWidth() const {
return (level <= 1) ? 0 : (level == 2) ? 24 : 20;
}
int getAdditionalHeight() const {
return (level <= 1) ? 1 : 0;
}
virtual void updateSizeForced(int maxWidth) override {
const int additionalWidth = getAdditionalWidth();
const int additionalHeight = getAdditionalHeight();
cachedMaxWidth = maxWidth;
child->updateSize(maxWidth - additionalWidth);
cachedWidth = child->cachedWidth + additionalWidth;
cachedNumberOfLines = child->cachedNumberOfLines + additionalHeight;
}
virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) override {
if (level <= 1) {
child->createSurfaces(surfaces, links);
int h = TTF_FontHeight(fontText);
SurfacePtr surface = createSurface(cachedWidth, h);
SDL_Color fg = objThemes.getTextColor(true);
Uint32 color = SDL_MapRGBA(surface->format, fg.r, fg.g, fg.b, 255);
if (level <= 0) {
SDL_Rect r[2] = {
{ 0, h / 2 - 3, cachedWidth, 2 },
{ 0, h / 2 + 1, cachedWidth, 2 },
};
SDL_FillRects(surface.get(), r, 2, color);
} else {
SDL_Rect r = { 0, h / 2 - 1, cachedWidth, 2 };
SDL_FillRect(surface.get(), &r, color);
}
surfaces.push_back(std::move(surface));
} else {
auto bmGUI = getImageManager().loadImage(getDataPath() + "gfx/gui.png");
const size_t oldLineCount = surfaces.size();
const size_t oldLinkSize = links.size();
std::vector<SurfacePtr> tempSurfaces;
child->createSurfaces(tempSurfaces, links);
const int additionalWidth = getAdditionalWidth();
for (size_t i = oldLinkSize; i < links.size(); i++) {
GUITextArea::Hyperlink2 &link = links[i];
link.line += oldLineCount;
link.startX += additionalWidth;
link.endX += additionalWidth;
}
for (int i = 0, m = tempSurfaces.size(); i < m; i++) {
SDL_Surface *src = tempSurfaces[i].get();
SurfacePtr surface = createSurface(src->w + additionalWidth, src->h);
SDL_Rect srcrect = { 0, 0, src->w, src->h };
SDL_Rect dstrect = { additionalWidth, 0, src->w, src->h };
SDL_BlitSurface(src, &srcrect, surface.get(), &dstrect);
if (i == 0) {
if (level == 2) {
srcrect = SDL_Rect{ 64, 0, 16, 16 };
} else {
srcrect = SDL_Rect{ 96, 16, 16, 16 };
}
dstrect = SDL_Rect{ (additionalWidth - 16) / 2, (src->h - 16) / 2, 16, 16 };
SDL_BlitSurface(bmGUI, &srcrect, surface.get(), &dstrect);
}
surfaces.push_back(std::move(surface));
}
}
}
};
class HelpPage {
public:
int level;
std::string title;
std::string nameSpace;
std::vector<Chunk*> chunks;
static const int BIGGEST_LEVEL = 10;
HelpPage() : level(0) {}
~HelpPage() {
for (auto chunk : chunks) {
delete chunk;
}
}
void show(SDL_Renderer& renderer, GUITextArea *textArea) {
SDL_Color fg = objThemes.getTextColor(true);
std::vector<SurfacePtr> surfaces;
std::vector<GUITextArea::Hyperlink2> links;
for (auto chunk : chunks) {
if (chunk) {
chunk->updateSize(textArea->width - 16);
chunk->createSurfaces(surfaces, links);
surfaces.emplace_back(TTF_RenderUTF8_Blended(fontText, " ", fg));
}
}
textArea->setStringArray(renderer, surfaces);
textArea->setHyperlinks(links);
}
};
void parseText(const std::vector<std::string>& lines, size_t start, size_t end, size_t startOfNonSpaces, std::string& output) {
bool init = false;
for (size_t i = start; i < end; i++) {
size_t lp = (i == start) ? startOfNonSpaces : 0;
const std::string& line = lines[i];
bool isSpace = true, isVerbatim = false;
for (; lp < line.size(); lp++) {
char c = line[lp];
if (c == ' ' || c == '\t') {
if (isVerbatim) output.push_back(' ');
isSpace = true;
} else {
if (!isVerbatim && init && isSpace) output.push_back(' ');
if (c == '`') isVerbatim = !isVerbatim;
output.push_back(c);
init = true;
isSpace = false;
}
}
}
}
ParagraphChunk* parseParagraph(const std::vector<std::string>& lines, size_t start, size_t end, size_t startOfNonSpaces) {
auto ret = new ParagraphChunk;
parseText(lines, start, end, startOfNonSpaces, ret->text);
return ret;
}
CodeChunk* parseCode(const std::vector<std::string>& lines, size_t start, size_t end) {
auto ret = new CodeChunk;
for (size_t i = start; i < end; i++) {
ret->lines.push_back(lines[i]);
}
return ret;
}
ItemizeChunk* parseItemize(const std::vector<std::string>& lines, size_t start, size_t end, char marker) {
auto ret = new ItemizeChunk;
size_t last = start, startOfNonSpaces = 0;
for (size_t i = start; i <= end; i++) {
bool isNewItem = false;
size_t lp = 0;
if (i < end) {
lp = lines[i].find_first_not_of(" \t");
if (lp != std::string::npos && lp + 1 < (int)lines[i].size() && lines[i][lp] == marker) {
char c = lines[i][lp + 1];
isNewItem = (c == ' ' || c == '\t');
}
} else {
isNewItem = true;
}
if (isNewItem) {
if (last != i) {
ret->items.push_back(parseParagraph(lines, last, i, startOfNonSpaces));
}
last = i;
startOfNonSpaces = lp + 1;
}
}
return ret;
}
void parseRow(const std::string& text, std::vector<std::string>& output) {
output.clear();
size_t lps = text.find_first_not_of(" \t");
if (lps == std::string::npos) return;
if (text[lps] == '|') lps++;
size_t lpe = text.find_last_not_of(" \t");
if (lpe < lps) return;
if (text[lpe] != '|') lpe++;
std::string item;
for (size_t lp = lps; lp <= lpe; lp++) {
char c = (lp < lpe) ? text[lp] : '|';
if (c == '|') {
output.push_back(item);
item.clear();
} else {
item.push_back(c);
}
}
}
TableChunk* parseTable(const std::vector<std::string>& lines, size_t start, size_t end) {
auto ret = new TableChunk;
std::vector<std::string> row;
//determine the number of columns
parseRow(lines[start], row);
int columns = row.size(), rows = 0;
if (columns > 0) {
ret->columns = columns;
ret->rows = rows = end - start - 1;
ret->items.resize(columns * rows, NULL);
for (int i = 0; i < columns; i++) {
ret->items[i] = parseParagraph(row, i, i + 1, 0);
}
for (int j = 1; j < rows; j++) {
parseRow(lines[start + j + 1], row);
for (int i = 0, m = std::min<int>(row.size(), columns); i < m; i++) {
ret->items[j * columns + i] = parseParagraph(row, i, i + 1, 0);
}
}
}
return ret;
}
HeaderChunk* parseHeader(const std::vector<std::string>& lines, size_t start, size_t end, size_t startOfNonSpaces, int level) {
auto ret = new HeaderChunk;
ret->level = level;
ret->child = parseParagraph(lines, start, end, startOfNonSpaces);
return ret;
}
Chunk* parseChunk(const std::vector<std::string>& lines, size_t& start) {
size_t end, startOfNonSpaces;
// skip empty lines
for (; start < lines.size(); start++) {
startOfNonSpaces = lines[start].find_first_not_of(" \t");
if (startOfNonSpaces != std::string::npos) break;
}
if (start >= lines.size()) return NULL;
// check if it's code
if (lines[start].find("~~~") == 0) {
start++;
if (start >= lines.size()) return NULL;
for (end = start; end < lines.size(); end++) {
if (lines[end].find("~~~") == 0) break;
}
auto ret = parseCode(lines, start, end);
start = end + 1;
return ret;
} else {
for (end = start; end < lines.size(); end++) {
if (lines[end].find_first_not_of(" \t") == std::string::npos) break;
}
}
if (end == start) return NULL;
// check if it's itemize
if (startOfNonSpaces + 1 < (int)lines[start].size()) {
char c = lines[start][startOfNonSpaces];
char c2 = lines[start][startOfNonSpaces + 1];
if ((c == '*' || c == '+' || c == '-') && (c2 == ' ' || c2 == '\t')) {
auto ret = parseItemize(lines, start, end, c);
start = end;
return ret;
}
}
// check if it's table
if (end - start >= 3 && lines[start].find_first_of('|') != std::string::npos && lines[start + 1].find_first_not_of(" \t-|") == std::string::npos) {
auto ret = parseTable(lines, start, end);
start = end;
return ret;
}
// check if it's header of syntax "### title"
if (lines[start][startOfNonSpaces] == '#') {
int level = 0;
for (; level < 5; level++) {
if ((size_t)(startOfNonSpaces + level + 1) >= lines[start].size()) {
level = -1;
break;
}
char c = lines[start][startOfNonSpaces + level + 1];
if (c == ' ' || c == '\t') break;
else if (c != '#') {
level = -1;
break;
}
}
if (level >= 0) {
auto ret = parseHeader(lines, start, end, startOfNonSpaces + level + 1, level);
start = end;
return ret;
}
}
// check if it's header of syntax "title\n==="
if (end - start >= 2) {
int level = -1;
if (lines[end - 1].find_first_not_of(" \t=") == std::string::npos) level = 0;
else if (lines[end - 1].find_first_not_of(" \t-") == std::string::npos) level = 1;
if (level >= 0) {
auto ret = parseHeader(lines, start, end - 1, startOfNonSpaces, level);
start = end;
return ret;
}
}
// now assume it's a normal paragraph
auto ret = parseParagraph(lines, start, end, startOfNonSpaces);
start = end;
return ret;
}
class HelpWindow : public GUIWindow {
public:
int currentPage;
std::vector<int> history;
int currentHistory;
public:
HelpWindow(ImageManager& imageManager, SDL_Renderer& renderer, int left = 0, int top = 0, int width = 0, int height = 0,
bool enabled = true, bool visible = true, const char* caption = NULL)
: GUIWindow(imageManager, renderer, left, top, width, height, enabled, visible, caption)
, currentPage(0)
, currentHistory(0)
{
}
};
HelpManager::HelpManager(GUIEventCallback *parent)
: parent(parent)
{
std::vector<std::string> lines;
{
std::ifstream fin((getDataPath() + "../docs/ScriptAPI.md").c_str());
if (fin) {
//Loop the lines of the file.
std::string line;
char c;
while (fin.get(c)) {
if (c == '\r') {
} else if (c == '\n') {
lines.push_back(line);
line.clear();
} else {
line.push_back(c);
}
}
lines.push_back(line);
} else {
std::cerr << "ERROR: Unable to open the ScriptAPI.md file." << std::endl;
lines.push_back("ERROR: Unable to open the ScriptAPI.md file.");
}
}
{
std::string library, nameSpace;
HelpPage *page = new HelpPage;
size_t start = 0;
while (auto chunk = parseChunk(lines, start)) {
if (HeaderChunk *header = dynamic_cast<HeaderChunk*>(chunk)) {
//We parse a new header so add a new page.
if (!page->chunks.empty()) {
pages.push_back(page);
page = new HelpPage;
}
page->level = header->level;
//Get the title.
if (auto par = dynamic_cast<ParagraphChunk*>(header->child)) {
page->title = par->text;
//Get the library name.
if (page->title.find("library") != std::string::npos) {
library.clear();
nameSpace.clear();
size_t lps = page->title.find_first_of('\"');
if (lps != std::string::npos) {
size_t lpe = page->title.find_last_of('\"');
if (lpe > lps) {
library = page->title.substr(lps + 1, lpe - lps - 1);
}
}
}
//Get the namespace.
if (page->title.find("Global") != std::string::npos) {
nameSpace.clear();
} else if (page->title.find("Static") != std::string::npos) {
nameSpace = library + ".";
} else if (page->title.find("Member") != std::string::npos) {
nameSpace = library + ":";
}
}
}
if (ItemizeChunk *itemize = library.empty() ? NULL : dynamic_cast<ItemizeChunk*>(chunk)) {
//We parse a new header so add a new page.
if (!page->chunks.empty()) {
pages.push_back(page);
page = new HelpPage;
}
page->level = HelpPage::BIGGEST_LEVEL;
page->nameSpace = nameSpace;
//Get the title.
if (ParagraphChunk *par = itemize->items.empty() ? NULL : dynamic_cast<ParagraphChunk*>(itemize->items[0])) {
page->title = par->text;
}
}
page->chunks.push_back(chunk);
}
if (page->chunks.empty()) delete page;
else pages.push_back(page);
}
//Normalize the title of each page
for (auto page : pages) {
if (page->title.find_first_of("(/") != std::string::npos || page->title.find("--") != std::string::npos) {
std::string tmp = page->title;
size_t lp = tmp.find("--");
if (lp != std::string::npos) tmp = tmp.substr(0, lp);
std::vector<std::string> names;
std::istringstream stream(tmp);
std::string line;
while (std::getline(stream, line, '/')) {
lp = line.find_first_of('(');
if (lp != std::string::npos) line = line.substr(0, lp);
lp = line.find_first_not_of(" \t");
if (lp != std::string::npos) {
line = line.substr(lp, line.find_last_not_of(" \t") - lp + 1);
if (std::find(names.begin(), names.end(), line) == names.end()) {
names.push_back(line);
}
}
}
tmp.clear();
for (const std::string& s : names) {
if (!tmp.empty()) tmp.push_back('/');
tmp += s;
}
page->title = tmp;
}
//Also normize the contents if necessary.
if (ItemizeChunk *itemize = page->chunks.empty() ? NULL : dynamic_cast<ItemizeChunk*>(page->chunks[0])) {
if (ParagraphChunk *par = itemize->items.empty() ? NULL : dynamic_cast<ParagraphChunk*>(itemize->items[0])) {
size_t lp = par->text.find("--");
if (lp != std::string::npos) {
std::vector<std::string> temp;
temp.push_back(par->text.substr(lp + 2));
page->chunks.push_back(parseParagraph(temp, 0, 1, 0));
lp = par->text.find_last_not_of(" \t", lp);
if (lp != std::string::npos) par->text = par->text.substr(0, lp);
}
}
}
}
//Add hyperlinks of subpages to each page
for (size_t i = 0; i < pages.size(); i++) {
auto page = pages[i];
int minLevel = 0, maxLevel = -1;
if (i == 0) {
maxLevel = 1;
} else {
minLevel = page->level + 1;
maxLevel = (page->level == 0) ? 1 : HelpPage::BIGGEST_LEVEL;
}
FakeTOCChunk *chunk = NULL;
int prevLevel = -1;
for (size_t j = i + 1; j < pages.size(); j++) {
auto subpage = pages[j];
if (subpage->level < minLevel) break;
if (subpage->level > maxLevel) continue;
int level;
if (subpage->level == HelpPage::BIGGEST_LEVEL) {
level = prevLevel + 1;
} else {
prevLevel = level = subpage->level - minLevel;
}
if (chunk == NULL) chunk = new FakeTOCChunk;
- chunk->items.push_back(FakeTOCItem{ subpage->title, level, j });
+ chunk->items.push_back(FakeTOCItem{ subpage->title, level, (int)j });
}
if (chunk) page->chunks.push_back(chunk);
}
}
HelpManager::~HelpManager() {
for (auto page : pages) {
delete page;
}
}
void HelpManager::updateCurrentPage(ImageManager& imageManager, SDL_Renderer& renderer, HelpWindow *window, int currentPage, bool addToHistory) {
auto sllb = dynamic_cast<GUISingleLineListBox*>(window->getChild("sllb"));
auto textArea = dynamic_cast<GUITextArea*>(window->getChild("TextArea"));
if (sllb && textArea && currentPage != window->currentPage) {
//Set current page
window->currentPage = currentPage;
//Update history
if (addToHistory) {
window->currentHistory++;
window->history.resize(window->currentHistory);
window->history.push_back(currentPage);
}
auto page = pages[currentPage];
//Show contents
page->show(renderer, textArea);
char s[32];
- sprintf(s, "%d/%d ", currentPage + 1, pages.size());
+ sprintf(s, "%d/%d ", currentPage + 1, (int)pages.size());
sllb->item[1].second = s + page->nameSpace + page->title;
sllb->value = 1;
}
}
GUIWindow* HelpManager::newWindow(ImageManager& imageManager, SDL_Renderer& renderer, int pageIndex) {
//Create the GUI.
HelpWindow* root = new HelpWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 500) / 2, 600, 500, true, true, _("Scripting Help"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "scriptingHelpWindow";
root->eventCallback = this;
GUIButton* btn;
const int BUTTON_SPACE = 70;
//Some navigation buttons
btn = new GUIButton(imageManager, renderer, root->width / 2 - BUTTON_SPACE * 3, 60, -1, 36, _("Homepage"), 0, true, true, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Homepage";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
btn = new GUIButton(imageManager, renderer, root->width / 2 - BUTTON_SPACE, 60, -1, 36, _("Back"), 0, true, true, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Back";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
btn = new GUIButton(imageManager, renderer, root->width / 2 + BUTTON_SPACE, 60, -1, 36, _("Forward"), 0, true, true, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Forward";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
btn = new GUIButton(imageManager, renderer, root->width / 2 + BUTTON_SPACE * 3, 60, -1, 36, _("Search"), 0, true, true, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Search";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
//Some buttons for search mode
btn = new GUIButton(imageManager, renderer, root->width / 2 - BUTTON_SPACE * 3, 60, -1, 36, _("Back"), 0, true, false, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Back2";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
btn = new GUIButton(imageManager, renderer, root->width / 2 + BUTTON_SPACE * 3, 60, -1, 36, _("Goto"), 0, true, false, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Goto";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
//Add a single line list box to select page directly
GUISingleLineListBox *sllb = new GUISingleLineListBox(imageManager, renderer, 25, 100, 550, 36);
sllb->gravityRight = GUIGravityRight;
sllb->name = "sllb";
sllb->item.resize(3);
sllb->value = 1;
sllb->eventCallback = root;
root->addChild(sllb);
//Add a text area.
GUITextArea *textArea = new GUITextArea(imageManager, renderer, 25, 140, 550, 300);
textArea->gravityRight = textArea->gravityBottom = GUIGravityRight;
textArea->name = "TextArea";
textArea->editable = false;
textArea->eventCallback = root;
root->addChild(textArea);
//Some widgets for search mode
GUITextBox *textBox = new GUITextBox(imageManager, renderer, 25, 100, 550, 36, NULL, 0, true, false);
textBox->gravityRight = GUIGravityRight;
textBox->name = "TextSearch";
textBox->eventCallback = root;
root->addChild(textBox);
GUIListBox *listBox = new GUIListBox(imageManager, renderer, 25, 140, 550, 300, true, false);
listBox->gravityRight = listBox->gravityBottom = GUIGravityRight;
listBox->name = "List";
root->addChild(listBox);
//The close button
btn = new GUIButton(imageManager, renderer, int(root->width*0.5f), 500 - 44, -1, 36, _("Close"), 0, true, true, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->gravityTop = btn->gravityBottom = GUIGravityRight;
btn->name = "cfgCancel";
btn->eventCallback = root;
root->addChild(btn);
//Show contents.
if (pageIndex < 0 || pageIndex >= (int)pages.size()) pageIndex = 0;
root->currentPage = -1;
updateCurrentPage(imageManager, renderer, root, pageIndex, false);
root->history.push_back(pageIndex);
return root;
}
void HelpManager::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name, GUIObject* obj, int eventType) {
HelpWindow *window = dynamic_cast<HelpWindow*>(obj);
if (name == "Homepage") {
if (SDL_GetModState() & KMOD_CTRL) {
GUIObjectRoot->addChild(newWindow(imageManager, renderer));
} else {
updateCurrentPage(imageManager, renderer, window, 0, true);
}
return;
} else if (name == "Back") {
if (window->currentHistory > 0) {
window->currentHistory--;
updateCurrentPage(imageManager, renderer, window, window->history[window->currentHistory], false);
}
return;
} else if (name == "Forward") {
if (window->currentHistory < (int)window->history.size() - 1) {
window->currentHistory++;
updateCurrentPage(imageManager, renderer, window, window->history[window->currentHistory], false);
}
return;
} else if (name == "sllb") {
auto sllb = dynamic_cast<GUISingleLineListBox*>(window->getChild("sllb"));
if (sllb && sllb->value != 1) {
int index = window->currentPage + sllb->value - 1;
sllb->value = 1;
if (index >= 0 && index < (int)pages.size()) {
if (SDL_GetModState() & KMOD_CTRL) {
GUIObjectRoot->addChild(newWindow(imageManager, renderer, index));
} else {
updateCurrentPage(imageManager, renderer, window, index, true);
}
}
}
return;
} else if (name == "Search" || name == "Back2") {
bool isSearch = name == "Search";
if (auto o = obj->getChild("Homepage")) o->visible = !isSearch;
if (auto o = obj->getChild("Back")) o->visible = !isSearch;
if (auto o = obj->getChild("Forward")) o->visible = !isSearch;
if (auto o = obj->getChild("Search")) o->visible = !isSearch;
if (auto o = obj->getChild("sllb")) o->visible = !isSearch;
if (auto o = obj->getChild("TextArea")) o->visible = !isSearch;
if (auto o = obj->getChild("Back2")) o->visible = isSearch;
if (auto o = obj->getChild("Goto")) o->visible = isSearch;
auto textBox = obj->getChild("TextSearch");
auto listBox = dynamic_cast<GUIListBox*>(obj->getChild("List"));
if (textBox && listBox) {
textBox->visible = isSearch;
listBox->visible = isSearch;
if (isSearch && listBox->item.empty()) {
updateListBox(imageManager, renderer, listBox, textBox->caption);
}
}
return;
} else if (name == "Goto") {
auto listBox = dynamic_cast<GUIListBox*>(obj->getChild("List"));
auto textArea = dynamic_cast<GUITextArea*>(obj->getChild("TextArea"));
if (listBox && textArea && listBox->value >= 0 && listBox->value < (int)listBox->item.size()) {
const std::string& s = listBox->item[listBox->value];
int index = atoi(s.c_str());
if (index >= 0 && index < (int)pages.size()) {
if (SDL_GetModState() & KMOD_CTRL) {
GUIObjectRoot->addChild(newWindow(imageManager, renderer, index));
} else {
updateCurrentPage(imageManager, renderer, window, index, true);
GUIEventQueue.push_back(GUIEvent{ this, "Back2", obj, GUIEventClick });
}
}
}
return;
} else if (name == "TextArea") {
auto textArea = dynamic_cast<GUITextArea*>(obj->getChild("TextArea"));
if (textArea && textArea->clickedHyperlink.size() > 5) {
std::string s = textArea->clickedHyperlink.substr(0, 5);
if (s == "code:") {
SDL_SetClipboardText(textArea->clickedHyperlink.c_str() + 5);
} else if (s == "page:") {
int index = atoi(textArea->clickedHyperlink.c_str() + 5);
if (index >= 0 && index < (int)pages.size()) {
if (SDL_GetModState() & KMOD_CTRL) {
GUIObjectRoot->addChild(newWindow(imageManager, renderer, index));
} else {
updateCurrentPage(imageManager, renderer, window, index, true);
}
}
}
}
return;
} else if (name == "TextSearch") {
auto textBox = obj->getChild("TextSearch");
auto listBox = dynamic_cast<GUIListBox*>(obj->getChild("List"));
if (textBox && listBox) {
updateListBox(imageManager, renderer, listBox, textBox->caption);
}
return;
}
//Do the default.
parent->GUIEventCallback_OnEvent(imageManager, renderer, name, obj, eventType);
}
void HelpManager::updateListBox(ImageManager& imageManager, SDL_Renderer& renderer, GUIListBox *listBox, const std::string& keyword) {
std::vector<std::string> keywords;
std::string line;
for (char c : keyword) {
if (c == ' ' || c == '\t') {
if (!line.empty()) keywords.push_back(line);
line.clear();
} else {
line.push_back(tolower((unsigned char)c));
}
}
if (!line.empty()) keywords.push_back(line);
int backup = listBox->value;
listBox->clearItems();
for (size_t i = 0; i < pages.size(); i++) {
HelpPage *page = pages[i];
bool match = true;
std::string tmp;
for (char c : page->title) {
tmp.push_back(tolower((unsigned char)c));
}
for (const std::string& s : keywords) {
if (tmp.find(s) == std::string::npos) {
match = false;
break;
}
}
if (match) {
SharedTexture tex;
SDL_Color fg = objThemes.getTextColor(true);
if (page->level == HelpPage::BIGGEST_LEVEL) {
SDL_Color fg2 = { Uint8((255 + fg.r) / 2), Uint8((255 + fg.g) / 2), Uint8((255 + fg.b) / 2), Uint8(255) };
SurfacePtr surf1(TTF_RenderUTF8_Blended(fontMono, (std::string(5, ' ') + page->nameSpace).c_str(), fg2));
SurfacePtr surf2(TTF_RenderUTF8_Blended(fontMono, page->title.empty() ? " " : page->title.c_str(), fg));
SurfacePtr surf = createSurface(surf1->w + surf2->w, std::max(surf1->h, surf2->h) + 4);
SDL_Rect srcrect = { 0, 0, surf1->w, surf1->h };
SDL_Rect dstrect = { 0, 2, surf1->w, surf1->h };
SDL_BlitSurface(surf1.get(), &srcrect, surf.get(), &dstrect);
srcrect = SDL_Rect{ 0, 0, surf2->w, surf2->h };
dstrect = SDL_Rect{ surf1->w, 2, surf2->w, surf2->h };
SDL_BlitSurface(surf2.get(), &srcrect, surf.get(), &dstrect);
tex = textureFromSurface(renderer, std::move(surf));
} else {
tex = textureFromTextShared(renderer, *fontText,
(std::string(page->level, ' ') + page->title).c_str(),
fg);
}
char s[32];
- sprintf(s, "%d", i);
+ sprintf(s, "%d", (int)i);
listBox->addItem(renderer, s, tex);
}
}
listBox->value = (backup >= 0 && backup < (int)listBox->item.size()) ? backup : -1;
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 9, 8:09 PM (6 d, 19 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
62716
Default Alt Text
(36 KB)

Event Timeline