Clean up RichLabel
This commit is contained in:
parent
151bc4c9cd
commit
4ab2a032af
@ -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)
|
||||||
{
|
{
|
||||||
|
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));
|
||||||
|
@ -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;
|
||||||
|
@ -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])
|
if (*it == ch)
|
||||||
{
|
{
|
||||||
char current = originalText[originalTextPos];
|
break;
|
||||||
|
}
|
||||||
if(state == ReadText)
|
++it;
|
||||||
|
}
|
||||||
|
return it;
|
||||||
|
};
|
||||||
|
auto beginRegionIt = find(it, '{');
|
||||||
|
auto beginDataIt = find(beginRegionIt, ':');
|
||||||
|
auto beginTextIt = find(beginDataIt, '|');
|
||||||
|
auto endRegionIt = find(beginTextIt, '}');
|
||||||
|
if (endRegionIt == newText.end())
|
||||||
{
|
{
|
||||||
if(current == '{')
|
break;
|
||||||
|
}
|
||||||
|
auto action = String(beginRegionIt + 1, beginDataIt);
|
||||||
|
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())
|
||||||
{
|
{
|
||||||
if(stackPos > 255)
|
RichTextRegion region;
|
||||||
throw RichTextParseException("Too many nested regions");
|
region.begin = sb.Size();
|
||||||
stackPos++;
|
sb << text;
|
||||||
regionsStack[stackPos].start = finalTextPos;
|
region.end = sb.Size();
|
||||||
regionsStack[stackPos].finish = finalTextPos;
|
region.action = RichTextRegion::LinkAction{ data.ToUtf8() };
|
||||||
state = ReadRegion;
|
newRegions.push_back(region);
|
||||||
|
good = true;
|
||||||
}
|
}
|
||||||
else if(current == '}')
|
if (!good)
|
||||||
{
|
{
|
||||||
if(stackPos >= 0)
|
sb << String(beginRegionIt, endRegionIt + 1);
|
||||||
{
|
|
||||||
currentData[currentDataPos] = 0;
|
|
||||||
regionsStack[stackPos].actionData = String(currentData);
|
|
||||||
regions.push_back(regionsStack[stackPos]);
|
|
||||||
stackPos--;
|
|
||||||
}
|
}
|
||||||
else
|
it = endRegionIt + 1;
|
||||||
{
|
|
||||||
throw RichTextParseException("Unexpected '}'");
|
|
||||||
}
|
}
|
||||||
}
|
sb << String(it, newText.end());
|
||||||
else
|
auto newDisplayText = sb.Build();
|
||||||
{
|
Label::SetText(format::CleanString(newDisplayText, false, true, false));
|
||||||
finalText[finalTextPos++] = current;
|
Label::SetDisplayText(newDisplayText);
|
||||||
finalText[finalTextPos] = 0;
|
regions = newRegions;
|
||||||
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 ®ion : regions)
|
for (auto const ®ion : 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>(®ion.action))
|
||||||
{
|
{
|
||||||
case 'a':
|
Platform::OpenURI(linkAction->uri);
|
||||||
Platform::OpenURI(region.actionData.ToUtf8());
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Label::OnMouseClick(x, y, button);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(); } });
|
||||||
|
Loading…
Reference in New Issue
Block a user