Clean up RichLabel

This commit is contained in:
Tamás Bálint Misius 2023-12-16 10:41:16 +01:00
parent 151bc4c9cd
commit 4ab2a032af
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
6 changed files with 76 additions and 206 deletions

View File

@ -17,8 +17,11 @@ Label::Label(Point position, Point size, String labelText):
multiline(false), multiline(false),
selecting(false), selecting(false),
autoHeight(size.Y==-1?true:false) autoHeight(size.Y==-1?true:false)
{
if (labelText.size()) // Don't call virtual function in ctor unless absolutely necessary. Deriveds set labelText to "".
{ {
SetText(labelText); SetText(labelText);
}
menu = new ContextMenu(this); menu = new ContextMenu(this);
menu->AddItem(ContextMenuItem("Copy", 0, true)); menu->AddItem(ContextMenuItem("Copy", 0, true));

View File

@ -58,7 +58,7 @@ namespace ui
void SetTextColour(Colour textColour) { this->textColour = textColour; } void SetTextColour(Colour textColour) { this->textColour = textColour; }
void OnContextMenuAction(int item) override; void OnContextMenuAction(int item) override;
void OnMouseClick(int x, int y, unsigned button) override; virtual void OnMouseClick(int x, int y, unsigned button) override;
void OnMouseUp(int x, int y, unsigned button) override; void OnMouseUp(int x, int y, unsigned button) override;
void OnMouseMoved(int localx, int localy, int dx, int dy) override; void OnMouseMoved(int localx, int localy, int dx, int dy) override;
void OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) override; void OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) override;

View File

@ -1,185 +1,67 @@
#include "RichLabel.h" #include "RichLabel.h"
#include "Colour.h"
#include "common/platform/Platform.h" #include "common/platform/Platform.h"
#include "graphics/FontReader.h" #include "Format.h"
#include "graphics/Graphics.h"
#include "gui/interface/Component.h"
#include "gui/interface/Point.h"
#include <vector>
#include <exception>
using namespace ui; using namespace ui;
struct RichTextParseException: public std::exception { RichLabel::RichLabel(Point position, Point size, String text) : Label(position, size, "")
ByteString message;
public:
RichTextParseException(String message = String("Parse error")): message(message.ToUtf8()) {}
const char * what() const throw() override
{ {
return message.c_str(); SetText(text);
} }
~RichTextParseException() throw() {};
void RichLabel::SetText(String newText)
{
Label::SetText(newText);
std::vector<RichTextRegion> newRegions;
StringBuilder sb;
auto it = newText.begin();
while (it != newText.end())
{
auto find = [&newText](auto it, String::value_type ch) {
while (it != newText.end())
{
if (*it == ch)
{
break;
}
++it;
}
return it;
}; };
auto beginRegionIt = find(it, '{');
RichLabel::RichLabel(Point position, Point size, String labelText): auto beginDataIt = find(beginRegionIt, ':');
Component(position, size), auto beginTextIt = find(beginDataIt, '|');
textSource(labelText), auto endRegionIt = find(beginTextIt, '}');
displayText("") if (endRegionIt == newText.end())
{ {
updateRichText(); break;
} }
auto action = String(beginRegionIt + 1, beginDataIt);
RichLabel::~RichLabel() auto data = String(beginDataIt + 1, beginTextIt);
auto text = String(beginTextIt + 1, endRegionIt);
sb << String(it, beginRegionIt);
auto good = false;
if (action == "a" || data.size() || text.size())
{ {
RichTextRegion region;
region.begin = sb.Size();
sb << text;
region.end = sb.Size();
region.action = RichTextRegion::LinkAction{ data.ToUtf8() };
newRegions.push_back(region);
good = true;
} }
if (!good)
void RichLabel::updateRichText()
{ {
regions.clear(); sb << String(beginRegionIt, endRegionIt + 1);
displayText = "";
if(textSource.length())
{
enum State { ReadText, ReadData, ReadRegion, ReadDataStart };
State state = ReadText;
int currentDataPos = 0;
String::value_type * currentData = new String::value_type[textSource.length()+1];
std::fill(currentData, currentData+textSource.length()+1, 0);
int finalTextPos = 0;
String::value_type * finalText = new String::value_type[textSource.length()+1];
std::fill(finalText, finalText+textSource.length()+1, 0);
int originalTextPos = 0;
String::value_type * originalText = new String::value_type[textSource.length()+1];
std::copy(textSource.begin(), textSource.end(), originalText);
originalText[textSource.length()] = 0;
int stackPos = -1;
RichTextRegion * regionsStack = new RichTextRegion[256];
try
{
while(originalText[originalTextPos])
{
char current = originalText[originalTextPos];
if(state == ReadText)
{
if(current == '{')
{
if(stackPos > 255)
throw RichTextParseException("Too many nested regions");
stackPos++;
regionsStack[stackPos].start = finalTextPos;
regionsStack[stackPos].finish = finalTextPos;
state = ReadRegion;
} }
else if(current == '}') it = endRegionIt + 1;
{
if(stackPos >= 0)
{
currentData[currentDataPos] = 0;
regionsStack[stackPos].actionData = String(currentData);
regions.push_back(regionsStack[stackPos]);
stackPos--;
} }
else sb << String(it, newText.end());
{ auto newDisplayText = sb.Build();
throw RichTextParseException("Unexpected '}'"); Label::SetText(format::CleanString(newDisplayText, false, true, false));
} Label::SetDisplayText(newDisplayText);
} regions = newRegions;
else
{
finalText[finalTextPos++] = current;
finalText[finalTextPos] = 0;
if(stackPos >= 0)
{
regionsStack[stackPos].finish = finalTextPos;
}
}
}
else if(state == ReadData)
{
if(current == '|')
{
state = ReadText;
}
else
{
currentData[currentDataPos++] = current;
currentData[currentDataPos] = 0;
}
}
else if(state == ReadDataStart)
{
if(current != ':')
{
throw RichTextParseException("Expected ':'");
}
state = ReadData;
currentDataPos = 0;
}
else if(state == ReadRegion)
{
if(stackPos >= 0)
{
regionsStack[stackPos].action = current;
state = ReadDataStart;
}
else
{
throw RichTextParseException();
}
}
originalTextPos++;
}
if(stackPos != -1)
throw RichTextParseException("Unclosed region");
finalText[finalTextPos] = 0;
displayText = String(finalText);
}
catch (const RichTextParseException & e)
{
displayText = "\br[Parse exception: " + ByteString(e.what()).FromUtf8() + "]";
regions.clear();
}
delete[] currentData;
delete[] finalText;
delete[] originalText;
delete[] regionsStack;
}
TextPosition(displayText);
displayTextWrapper.Update(displayText, false, 0);
}
void RichLabel::SetText(String text)
{
textSource = text;
updateRichText();
}
String RichLabel::GetDisplayText()
{
return displayText;
}
String RichLabel::GetText()
{
return textSource;
}
void RichLabel::Draw(const Point& screenPos)
{
Graphics * g = GetGraphics();
ui::Colour textColour = Appearance.TextInactive;
g->BlendText(screenPos + textPosition, displayText, textColour.NoAlpha().WithAlpha(255));
} }
void RichLabel::OnMouseClick(int x, int y, unsigned button) void RichLabel::OnMouseClick(int x, int y, unsigned button)
@ -187,14 +69,14 @@ void RichLabel::OnMouseClick(int x, int y, unsigned button)
int cursorPosition = displayTextWrapper.Point2Index(x - textPosition.X, y - textPosition.Y).raw_index; int cursorPosition = displayTextWrapper.Point2Index(x - textPosition.X, y - textPosition.Y).raw_index;
for (auto const &region : regions) for (auto const &region : regions)
{ {
if (region.start <= cursorPosition && region.finish >= cursorPosition) if (region.begin <= cursorPosition && region.end > cursorPosition)
{ {
switch (region.action) if (auto *linkAction = std::get_if<RichTextRegion::LinkAction>(&region.action))
{ {
case 'a': Platform::OpenURI(linkAction->uri);
Platform::OpenURI(region.actionData.ToUtf8()); return;
break;
} }
} }
} }
Label::OnMouseClick(x, y, button);
} }

View File

@ -1,41 +1,29 @@
#pragma once #pragma once
#include "Label.h"
#include "common/String.h" #include <vector>
#include "Component.h" #include <variant>
#include "TextWrapper.h"
namespace ui namespace ui
{ {
class RichLabel : public Component class RichLabel : public Label
{ {
public:
struct RichTextRegion struct RichTextRegion
{ {
int start; int begin;
int finish; int end;
int action; struct LinkAction
String actionData; {
ByteString uri;
};
using Action = std::variant<LinkAction>;
Action action;
}; };
TextWrapper displayTextWrapper;
RichLabel(Point position, Point size, String richText);
virtual ~RichLabel();
void SetText(String text);
String GetDisplayText();
String GetText();
void Draw(const Point& screenPos) override;
void OnMouseClick(int x, int y, unsigned button) override;
protected:
String textSource;
String displayText;
std::vector<RichTextRegion> regions; std::vector<RichTextRegion> regions;
void updateRichText(); public:
RichLabel(Point position, Point size, String text);
void SetText(String newText) override;
void OnMouseClick(int x, int y, unsigned button) override;
}; };
} }

View File

@ -63,6 +63,7 @@ namespace ui
switch (*it) // set sequence_length if *it starts a sequence that should be forwarded as-is switch (*it) // set sequence_length if *it starts a sequence that should be forwarded as-is
{ {
case '\b': sequence_length = 2; break; case '\b': sequence_length = 2; break;
case '\x0e': sequence_length = 1; break;
case '\x0f': sequence_length = 4; break; case '\x0f': sequence_length = 4; break;
} }

View File

@ -30,11 +30,7 @@ SearchView::SearchView():
nextButton = new ui::Button(ui::Point(WINDOWW-52, WINDOWH-18), ui::Point(50, 16), String("Next ") + 0xE015); nextButton = new ui::Button(ui::Point(WINDOWW-52, WINDOWH-18), ui::Point(50, 16), String("Next ") + 0xE015);
previousButton = new ui::Button(ui::Point(2, WINDOWH-18), ui::Point(50, 16), 0xE016 + String(" Prev")); previousButton = new ui::Button(ui::Point(2, WINDOWH-18), ui::Point(50, 16), 0xE016 + String(" Prev"));
tagsLabel = new ui::Label(ui::Point(270, WINDOWH-18), ui::Point(WINDOWW-540, 16), "\boPopular Tags:"); tagsLabel = new ui::Label(ui::Point(270, WINDOWH-18), ui::Point(WINDOWW-540, 16), "\boPopular Tags:");
try
{
motdLabel = new ui::RichLabel(ui::Point(51, WINDOWH-18), ui::Point(WINDOWW-102, 16), Client::Ref().GetMessageOfTheDay()); motdLabel = new ui::RichLabel(ui::Point(51, WINDOWH-18), ui::Point(WINDOWW-102, 16), Client::Ref().GetMessageOfTheDay());
}
catch (std::exception & e) { }
pageTextbox = new ui::Textbox(ui::Point(283, WINDOWH-18), ui::Point(41, 16), ""); pageTextbox = new ui::Textbox(ui::Point(283, WINDOWH-18), ui::Point(41, 16), "");
pageTextbox->SetActionCallback({ [this] { textChanged(); } }); pageTextbox->SetActionCallback({ [this] { textChanged(); } });