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

@ -18,7 +18,10 @@ Label::Label(Point position, Point size, String labelText):
selecting(false), selecting(false),
autoHeight(size.Y==-1?true:false) autoHeight(size.Y==-1?true:false)
{ {
SetText(labelText); if (labelText.size()) // Don't call virtual function in ctor unless absolutely necessary. Deriveds set labelText to "".
{
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();
}
~RichTextParseException() throw() {};
};
RichLabel::RichLabel(Point position, Point size, String labelText):
Component(position, size),
textSource(labelText),
displayText("")
{ {
updateRichText(); SetText(text);
} }
RichLabel::~RichLabel() void RichLabel::SetText(String newText)
{ {
Label::SetText(newText);
} std::vector<RichTextRegion> newRegions;
StringBuilder sb;
void RichLabel::updateRichText() auto it = newText.begin();
{ while (it != newText.end())
regions.clear();
displayText = "";
if(textSource.length())
{ {
auto find = [&newText](auto it, String::value_type ch) {
enum State { ReadText, ReadData, ReadRegion, ReadDataStart }; while (it != newText.end())
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 (*it == ch)
if(state == ReadText)
{ {
if(current == '{') break;
{
if(stackPos > 255)
throw RichTextParseException("Too many nested regions");
stackPos++;
regionsStack[stackPos].start = finalTextPos;
regionsStack[stackPos].finish = finalTextPos;
state = ReadRegion;
}
else if(current == '}')
{
if(stackPos >= 0)
{
currentData[currentDataPos] = 0;
regionsStack[stackPos].actionData = String(currentData);
regions.push_back(regionsStack[stackPos]);
stackPos--;
}
else
{
throw RichTextParseException("Unexpected '}'");
}
}
else
{
finalText[finalTextPos++] = current;
finalText[finalTextPos] = 0;
if(stackPos >= 0)
{
regionsStack[stackPos].finish = finalTextPos;
}
}
} }
else if(state == ReadData) ++it;
{
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++;
} }
return it;
if(stackPos != -1) };
throw RichTextParseException("Unclosed region"); auto beginRegionIt = find(it, '{');
auto beginDataIt = find(beginRegionIt, ':');
finalText[finalTextPos] = 0; auto beginTextIt = find(beginDataIt, '|');
displayText = String(finalText); auto endRegionIt = find(beginTextIt, '}');
} if (endRegionIt == newText.end())
catch (const RichTextParseException & e)
{ {
displayText = "\br[Parse exception: " + ByteString(e.what()).FromUtf8() + "]"; break;
regions.clear();
} }
delete[] currentData; auto action = String(beginRegionIt + 1, beginDataIt);
delete[] finalText; auto data = String(beginDataIt + 1, beginTextIt);
delete[] originalText; auto text = String(beginTextIt + 1, endRegionIt);
delete[] regionsStack; 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)
{
sb << String(beginRegionIt, endRegionIt + 1);
}
it = endRegionIt + 1;
} }
TextPosition(displayText); sb << String(it, newText.end());
displayTextWrapper.Update(displayText, false, 0); auto newDisplayText = sb.Build();
} Label::SetText(format::CleanString(newDisplayText, false, true, false));
Label::SetDisplayText(newDisplayText);
void RichLabel::SetText(String text) regions = newRegions;
{
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(); } });