enhanced StaticTextWidget to display links

adapted AboutDialog accordingly
This commit is contained in:
thrust26 2021-04-22 11:43:08 +02:00
parent b945e15adc
commit f641457083
6 changed files with 189 additions and 17 deletions

View File

@ -383,7 +383,8 @@ int FBSurface::drawString(const GUI::Font& font, const string& s,
void FBSurface::drawString(const GUI::Font& font, const string& s, void FBSurface::drawString(const GUI::Font& font, const string& s,
int x, int y, int w, int x, int y, int w,
ColorId color, TextAlign align, ColorId color, TextAlign align,
int deltax, bool useEllipsis, ColorId shadowColor) int deltax, bool useEllipsis, ColorId shadowColor,
size_t linkStart, int linkLen, bool underline)
{ {
#ifdef GUI_SUPPORT #ifdef GUI_SUPPORT
const string ELLIPSIS = "\x1d"; // "..." const string ELLIPSIS = "\x1d"; // "..."
@ -424,16 +425,30 @@ void FBSurface::drawString(const GUI::Font& font, const string& s,
x = x + w - width; x = x + w - width;
x += deltax; x += deltax;
int x0 = 0, x1 = 0;
for(i = 0; i < str.size(); ++i) for(i = 0; i < str.size(); ++i)
{ {
w = font.getCharWidth(str[i]); w = font.getCharWidth(str[i]);
if(x + w > rightX) if(x + w > rightX)
break; break;
if(x >= leftX) if(x >= leftX)
drawChar(font, str[i], x, y, color, shadowColor); {
if(i == linkStart)
x0 = x;
else if(i < linkStart + linkLen)
x1 = x + w;
drawChar(font, str[i], x, y,
(i >= linkStart && i < linkStart + linkLen) ? kTextColorEm : color,
shadowColor);
}
x += w; x += w;
} }
if(underline && x1 > 0)
hLine(x0, y + font.getFontHeight() - 1, x1, kTextColorEm);
#endif #endif
} }

View File

@ -245,7 +245,8 @@ class FBSurface
virtual void drawString( virtual void drawString(
const GUI::Font& font, const string& s, int x, int y, int w, const GUI::Font& font, const string& s, int x, int y, int w,
ColorId color, TextAlign align = TextAlign::Left, ColorId color, TextAlign align = TextAlign::Left,
int deltax = 0, bool useEllipsis = true, ColorId shadowColor = kNone); int deltax = 0, bool useEllipsis = true, ColorId shadowColor = kNone,
size_t linkStart = string::npos, int linkLen = 0, bool underline = false);
/** /**
Splits a given string to a given width considering whitespaces. Splits a given string to a given width considering whitespaces.

View File

@ -21,6 +21,7 @@
#include "Widget.hxx" #include "Widget.hxx"
#include "Font.hxx" #include "Font.hxx"
#include "WhatsNewDialog.hxx" #include "WhatsNewDialog.hxx"
#include "MediaFactory.hxx"
#include "AboutDialog.hxx" #include "AboutDialog.hxx"
@ -80,8 +81,10 @@ AboutDialog::AboutDialog(OSystem& osystem, DialogContainer& parent,
xpos = HBORDER * 2; ypos += lineHeight + VGAP * 2; xpos = HBORDER * 2; ypos += lineHeight + VGAP * 2;
for(int i = 0; i < myLinesPerPage; i++) for(int i = 0; i < myLinesPerPage; i++)
{ {
myDesc.push_back(new StaticTextWidget(this, font, xpos, ypos, _w - xpos * 2, StaticTextWidget* s = new StaticTextWidget(this, font, xpos, ypos, _w - xpos * 2,
fontHeight, "", TextAlign::Left)); fontHeight, "", TextAlign::Left, kNone);
s->setID(i);
myDesc.push_back(s);
myDescStr.emplace_back(""); myDescStr.emplace_back("");
ypos += fontHeight; ypos += fontHeight;
} }
@ -162,7 +165,7 @@ void AboutDialog::updateStrings(int page, int lines, string& title)
title = "Cast of thousands"; title = "Cast of thousands";
ADD_ATEXT("\\L\\c0""Special thanks to AtariAge for introducing the"); ADD_ATEXT("\\L\\c0""Special thanks to AtariAge for introducing the");
ADD_ATEXT("\\L\\c0""Atari 2600 to a whole new generation."); ADD_ATEXT("\\L\\c0""Atari 2600 to a whole new generation.");
ADD_ATEXT("\\L\\c2"" http://www.atariage.com"); ADD_ATEXT("\\L http://www.atariage.com");
ADD_ALINE(); ADD_ALINE();
ADD_ATEXT("\\L\\c0""Finally, a huge thanks to the original Atari 2600"); ADD_ATEXT("\\L\\c0""Finally, a huge thanks to the original Atari 2600");
ADD_ATEXT("\\L\\c0""VCS team for giving us the magic, and to the"); ADD_ATEXT("\\L\\c0""VCS team for giving us the magic, and to the");
@ -242,6 +245,7 @@ void AboutDialog::displayInfo()
myDesc[i]->setAlign(align); myDesc[i]->setAlign(align);
myDesc[i]->setTextColor(color); myDesc[i]->setTextColor(color);
myDesc[i]->setLabel(str); myDesc[i]->setLabel(str);
myDesc[i]->setUrl(); // extract URL from label
} }
// Redraw entire dialog // Redraw entire dialog
@ -280,7 +284,52 @@ void AboutDialog::handleCommand(CommandSender* sender, int cmd, int data, int id
myWhatsNewDialog->open(); myWhatsNewDialog->open();
break; break;
case StaticTextWidget::kOpenUrlCmd:
{
const string url = myDesc[id]->getUrl(); // getUrl(myDescStr[id]);
if(url != EmptyString)
MediaFactory::openURL(url);
break;
}
default: default:
Dialog::handleCommand(sender, cmd, data, 0); Dialog::handleCommand(sender, cmd, data, 0);
} }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const string AboutDialog::getUrl(const string& str) const
{
bool isUrl = false;
int start = 0, len = 0;
for(int i = 0; i < str.size(); ++i)
{
string remainder = str.substr(i);
char ch = str[i];
if(!isUrl
&& (BSPF::startsWithIgnoreCase(remainder, "http://")
|| BSPF::startsWithIgnoreCase(remainder, "https://")
|| BSPF::startsWithIgnoreCase(remainder, "www.")))
{
isUrl = true;
start = i;
}
// hack, change mode without changing string length
if(isUrl)
{
if((ch == ' ' || ch == ')' || ch == '>'))
isUrl = false;
else
len++;
}
}
if(len)
return str.substr(start, len);
else
return EmptyString;
}

View File

@ -38,6 +38,7 @@ class AboutDialog : public Dialog
void handleCommand(CommandSender* sender, int cmd, int data, int id) override; void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
void updateStrings(int page, int lines, string& title); void updateStrings(int page, int lines, string& title);
void displayInfo(); void displayInfo();
const string getUrl(const string& text) const;
void loadConfig() override { displayInfo(); } void loadConfig() override { displayInfo(); }

View File

@ -462,6 +462,7 @@ StaticTextWidget::StaticTextWidget(GuiObject* boss, const GUI::Font& font,
const string& text, TextAlign align, const string& text, TextAlign align,
ColorId shadowColor) ColorId shadowColor)
: Widget(boss, font, x, y, w, h), : Widget(boss, font, x, y, w, h),
CommandSender(boss),
_label{text}, _label{text},
_align{align} _align{align}
{ {
@ -472,6 +473,7 @@ StaticTextWidget::StaticTextWidget(GuiObject* boss, const GUI::Font& font,
_textcolor = kTextColor; _textcolor = kTextColor;
_textcolorhi = kTextColor; _textcolorhi = kTextColor;
_shadowcolor = shadowColor; _shadowcolor = shadowColor;
_cmd = 0;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -500,29 +502,116 @@ void StaticTextWidget::setLabel(const string& label)
} }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void StaticTextWidget::setLink(size_t start, int len, bool underline)
{
if(_linkStart != start || _linkLen != len || _linkUnderline != underline)
{
_linkStart = start;
_linkLen = len;
_linkUnderline = underline;
setCmd(len ? kClickedCmd : 0);
setDirty();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool StaticTextWidget::setUrl(const string& url, const string& label)
{
size_t start = string::npos;
int len = 0;
const string text = label != EmptyString ? label : url;
if(text != EmptyString)
{
// determine position of label
if((start = BSPF::findIgnoreCase(_label, text)) != string::npos)
{
len = int(text.size());
_url = url;
}
}
else
{
// extract URL from _label
start = BSPF::findIgnoreCase(_label, "http://");
if(start == string::npos)
start = BSPF::findIgnoreCase(_label, "https://");
if(start == string::npos)
start = BSPF::findIgnoreCase(_label, "www.");
if(start != string::npos)
{
// find end of URL
for(int i = int(start); i < _label.size(); ++i)
{
char ch = _label[i];
if(ch == ' ' || ch == ')' || ch == '>')
{
len = i - int(start);
_url = _label.substr(start, len);
break;
}
}
if(!len)
{
len = int(_label.size() - start);
_url = _label.substr(start);
}
}
}
if(len)
{
setLink(start, len, true);
setCmd(kOpenUrlCmd);
return true;
}
else
{
setLink(); // clear link
_url = EmptyString;
return false;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void StaticTextWidget::handleMouseEntered() void StaticTextWidget::handleMouseEntered()
{ {
if(isEnabled()) if(isEnabled())
// Mouse focus for tooltips must not change dirty status setFlags(Widget::FLAG_HILITED | Widget::FLAG_MOUSE_FOCUS, _linkLen);
setFlags(Widget::FLAG_MOUSE_FOCUS, false);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void StaticTextWidget::handleMouseLeft() void StaticTextWidget::handleMouseLeft()
{ {
if(isEnabled()) if(isEnabled())
// Mouse focus for tooltips must not change dirty status clearFlags(Widget::FLAG_HILITED | Widget::FLAG_MOUSE_FOCUS, _linkLen);
clearFlags(Widget::FLAG_MOUSE_FOCUS, false);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void StaticTextWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount)
{
if(_cmd && isEnabled() && x >= 0 && x < _w && y >= 0 && y < _h)
{
clearFlags(Widget::FLAG_HILITED);
sendCommand(_cmd, 0, _id);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void StaticTextWidget::drawWidget(bool hilite) void StaticTextWidget::drawWidget(bool hilite)
{ {
FBSurface& s = _boss->dialog().surface(); FBSurface& s = _boss->dialog().surface();
s.drawString(_font, _label, _x, _y, _w, s.drawString(_font, _label, _x, _y, _w,
isEnabled() ? _textcolor : kColor, _align, 0, true, _shadowcolor); isEnabled() ? _textcolor : kColor, _align, 0, true,
_shadowcolor, _linkStart, _linkLen, _linkUnderline && hilite);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -530,7 +619,6 @@ ButtonWidget::ButtonWidget(GuiObject* boss, const GUI::Font& font,
int x, int y, int w, int h, int x, int y, int w, int h,
const string& label, int cmd, bool repeat) const string& label, int cmd, bool repeat)
: StaticTextWidget(boss, font, x, y, w, h, label, TextAlign::Center), : StaticTextWidget(boss, font, x, y, w, h, label, TextAlign::Center),
CommandSender(boss),
_cmd{cmd}, _cmd{cmd},
_repeat{repeat} _repeat{repeat}
{ {

View File

@ -191,8 +191,14 @@ class Widget : public GuiObject
}; };
/* StaticTextWidget */ /* StaticTextWidget */
class StaticTextWidget : public Widget class StaticTextWidget : public Widget, public CommandSender
{ {
public:
enum {
kClickedCmd = 'STcl',
kOpenUrlCmd = 'STou'
};
public: public:
StaticTextWidget(GuiObject* boss, const GUI::Font& font, StaticTextWidget(GuiObject* boss, const GUI::Font& font,
int x, int y, int w, int h, int x, int y, int w, int h,
@ -204,8 +210,7 @@ class StaticTextWidget : public Widget
ColorId shadowColor = kNone); ColorId shadowColor = kNone);
~StaticTextWidget() override = default; ~StaticTextWidget() override = default;
void handleMouseEntered() override; void setCmd(int cmd) { _cmd = cmd; }
void handleMouseLeft() override;
void setValue(int value); void setValue(int value);
void setLabel(const string& label); void setLabel(const string& label);
@ -213,13 +218,26 @@ class StaticTextWidget : public Widget
const string& getLabel() const { return _label; } const string& getLabel() const { return _label; }
bool isEditable() const { return _editable; } bool isEditable() const { return _editable; }
void setLink(size_t start = string::npos, int len = 0, bool underline = false);
bool setUrl(const string& url = EmptyString, const string& label = EmptyString);
const string& getUrl() const { return _url; };
protected: protected:
void handleMouseEntered() override;
void handleMouseLeft() override;
void handleMouseUp(int x, int y, MouseButton b, int clickCount) override;
void drawWidget(bool hilite) override; void drawWidget(bool hilite) override;
protected: protected:
string _label; string _label;
bool _editable{false}; bool _editable{false};
TextAlign _align{TextAlign::Left}; TextAlign _align{TextAlign::Left};
int _cmd{0};
size_t _linkStart{string::npos};
int _linkLen{0};
bool _linkUnderline{false};
string _url;
private: private:
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported
@ -231,7 +249,7 @@ class StaticTextWidget : public Widget
}; };
/* ButtonWidget */ /* ButtonWidget */
class ButtonWidget : public StaticTextWidget, public CommandSender class ButtonWidget : public StaticTextWidget
{ {
public: public:
ButtonWidget(GuiObject* boss, const GUI::Font& font, ButtonWidget(GuiObject* boss, const GUI::Font& font,