1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-11-02 15:13:50 +01:00
Files
audacity/src/ui/AccessibleLinksFormatter.cpp
Dmitry Vedenko 73e9b4dcfe Adds a class that allows keyboard accessible links in the text.
ShuttleGUI::AddWindow has a new argument now: positionFlags. This argument does not change the default behavior.

StartWrapLay/EndWrapLay are added, so wxWrapSizer can be used from the ShuttleGUI.
2021-07-19 05:45:54 +00:00

186 lines
4.8 KiB
C++

/*!********************************************************************
Audacity: A Digital Audio Editor
@file AccessibleLinksFormatter.h
@brief Define a helper class to format text with link in a way, accessible to VI users.
Dmitry Vedenko
**********************************************************************/
#include "AccessibleLinksFormatter.h"
#include "ShuttleGui.h"
#include <unordered_map>
#include <algorithm>
#include <wx/hyperlink.h>
namespace
{
size_t OffsetPosition(size_t position, size_t length)
{
if (position == wxString::npos)
return wxString::npos;
return position + length;
}
}
AccessibleLinksFormatter::AccessibleLinksFormatter(TranslatableString message)
: mMessage(std::move(message))
{
}
AccessibleLinksFormatter& AccessibleLinksFormatter::FormatLink(
wxString placeholder, TranslatableString value, std::string targetURL)
{
mFormatArguments.push_back({
std::move(placeholder),
std::move(value),
{},
std::move(targetURL)
});
return *this;
}
AccessibleLinksFormatter& AccessibleLinksFormatter::FormatLink(
wxString placeholder, TranslatableString value,
LinkClickedHandler handler)
{
mFormatArguments.push_back({
std::move(placeholder),
std::move(value),
std::move(handler),
{}
});
return *this;
}
void AccessibleLinksFormatter::Populate(ShuttleGui& S) const
{
// Just add the text, if there are no links to format
if (mFormatArguments.empty())
{
S.AddFixedText(mMessage);
return;
}
wxString translated = mMessage.Translation();
std::vector<ProcessedArgument> processedArguments =
ProcessArguments(translated);
if (processedArguments.empty())
{
S.AddFixedText(mMessage);
return;
}
const int borderSize = S.GetBorder();
S.Prop(0).StartInvisiblePanel();
S.StartWrapLay();
{
size_t currentPosition = 0;
S.SetBorder(0);
if (borderSize > 0)
S.AddSpace(borderSize);
for (const ProcessedArgument& processedArgument : processedArguments)
{
const FormatArgument* argument = processedArgument.Argument;
// Add everything between currentPosition and PlaceholderPosition
if (currentPosition != processedArgument.PlaceholderPosition)
{
const size_t substrLength =
processedArgument.PlaceholderPosition - currentPosition;
S.Prop(0).AddFixedText(
Verbatim(translated.substr(currentPosition, substrLength)));
}
// Add hyperlink
wxHyperlinkCtrl* hyperlink = safenew wxHyperlinkCtrl(
S.GetParent(), wxID_ANY, argument->Value.Translation(),
argument->TargetURL);
if (argument->Handler)
{
hyperlink->Bind(
wxEVT_HYPERLINK, [handler = argument->Handler](wxHyperlinkEvent& evt) {
handler();
});
}
S.AddWindow(hyperlink, wxALIGN_TOP | wxALIGN_LEFT);
// Update the currentPostion to the first symbol after the Placeholder
currentPosition = OffsetPosition(
processedArgument.PlaceholderPosition,
argument->Placeholder.Length());
if (currentPosition >= translated.Length())
break;
}
if (currentPosition < translated.Length())
S.AddFixedText(Verbatim(translated.substr(currentPosition)));
}
S.EndWrapLay();
S.EndInvisiblePanel();
S.SetBorder(borderSize);
}
std::vector<AccessibleLinksFormatter::ProcessedArgument>
AccessibleLinksFormatter::ProcessArguments(wxString translatedMessage) const
{
std::vector<ProcessedArgument> result;
result.reserve(mFormatArguments.size());
// Arguments with the same placeholder are processed left-to-right.
// Lets track the last known position of the placeholder
std::unordered_map<wxString, size_t> knownPlaceholderPosition;
for (const FormatArgument& argument : mFormatArguments)
{
auto it = knownPlaceholderPosition.find(argument.Placeholder);
const size_t startingPosition =
it != knownPlaceholderPosition.end() ?
OffsetPosition(it->second, argument.Placeholder.length()) :
0;
const size_t placeholderPosition =
startingPosition == wxString::npos ?
wxString::npos :
translatedMessage.find(argument.Placeholder, startingPosition);
knownPlaceholderPosition[argument.Placeholder] = placeholderPosition;
if (placeholderPosition != wxString::npos)
{
result.emplace_back(
ProcessedArgument { &argument, placeholderPosition });
}
}
std::sort(
result.begin(), result.end(),
[](const ProcessedArgument& lhs, const ProcessedArgument& rhs) {
return lhs.PlaceholderPosition < rhs.PlaceholderPosition;
});
return result;
}