Audacity 3.2.0
AccessibleLinksFormatter.cpp
Go to the documentation of this file.
1/*!********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file AccessibleLinksFormatter.h
6 @brief Define a helper class to format text with link in a way, accessible to VI users.
7
8 Dmitry Vedenko
9 **********************************************************************/
10
12
13#include "ShuttleGui.h"
14
15#include <unordered_map>
16#include <algorithm>
17#include <memory>
18
19#include "BasicUI.h"
20
21#include <wx/hyperlink.h>
22
23#ifdef __WXGTK__
24#include <wx/stattext.h>
25#endif
26
27namespace
28{
29size_t OffsetPosition(size_t position, size_t length)
30{
31 if (position == wxString::npos)
32 return wxString::npos;
33
34 return position + length;
35}
36}
37
39 : mMessage(std::move(message))
40{
41}
42
44 wxString placeholder, TranslatableString value, std::string targetURL)
45{
46 mFormatArguments.push_back({
47 std::move(placeholder),
48 std::move(value),
49 {},
50 std::move(targetURL)
51 });
52
53 return *this;
54}
55
57 wxString placeholder, TranslatableString value,
59{
60 mFormatArguments.push_back({
61 std::move(placeholder),
62 std::move(value),
63 std::move(handler),
64 {}
65 });
66
67 return *this;
68}
69
71{
72 // Just add the text, if there are no links to format
73 if (mFormatArguments.empty())
74 {
75 S.AddFixedText(mMessage);
76 return;
77 }
78
79#ifdef __WXGTK__
80 // Non empty label is required, as wxHyperlinkCtrl would assert otherwise
81 std::unique_ptr<wxHyperlinkCtrl> tempHyperlink
82 = std::make_unique<wxHyperlinkCtrl>(S.GetParent(), wxID_ANY, wxT("temp"), wxString());
83
84 const wxColour hyperlinkColour = tempHyperlink->GetNormalColour();
85
86 tempHyperlink.reset();
87#endif
88
89 wxString translated = mMessage.Translation();
90
91 std::vector<ProcessedArgument> processedArguments =
92 ProcessArguments(translated);
93
94 if (processedArguments.empty())
95 {
96 S.AddFixedText(mMessage);
97 return;
98 }
99
100 const int borderSize = S.GetBorder();
101
102 S.StartHorizontalLay(wxEXPAND);
103 {
104 S.SetBorder(0);
105 S.AddSpace(borderSize);
106
107 S.StartWrapLay(wxEXPAND, 1);
108 {
109 size_t currentPosition = 0;
110
111 for (const ProcessedArgument& processedArgument : processedArguments)
112 {
113 const FormatArgument* argument = processedArgument.Argument;
114
115 // Add everything between currentPosition and PlaceholderPosition
116
117 if (currentPosition != processedArgument.PlaceholderPosition)
118 {
119 const size_t substrLength =
120 processedArgument.PlaceholderPosition - currentPosition;
121
122 S.Prop(0).AddFixedText(
123 Verbatim(translated.substr(currentPosition, substrLength)));
124 }
125
126 // Add hyperlink
127#ifndef __WXGTK__
128 const auto value = argument->Value.Translation();
129 // On macOS wx refuses to create wxHyperlinkCtrl with an empty value
130 if (!value.empty())
131 {
132 wxHyperlinkCtrl* hyperlink = safenew wxHyperlinkCtrl(
133 S.GetParent(), wxID_ANY, argument->Value.Translation(),
134 argument->TargetURL);
135
136 hyperlink->Bind(
137 wxEVT_HYPERLINK,
138 [handler = argument->Handler,
139 url = argument->TargetURL](wxHyperlinkEvent& evt)
140 {
141 if (handler)
142 handler();
143 else if (!url.empty())
144 BasicUI::OpenInDefaultBrowser(url);
145
146 });
147
148
149 S.AddWindow(hyperlink, wxALIGN_TOP | wxALIGN_LEFT);
150 }
151#else
152 wxStaticText* hyperlink = S.AddVariableText(argument->Value);
153
154 hyperlink->SetFont(hyperlink->GetFont().Underlined());
155 hyperlink->SetForegroundColour(hyperlinkColour);
156 hyperlink->SetCursor(wxCURSOR_HAND);
157
158 hyperlink->Bind(wxEVT_LEFT_UP, [handler = argument->Handler, url = argument->TargetURL](wxEvent&) {
159 if (handler)
160 handler();
161 else if (!url.empty())
162 BasicUI::OpenInDefaultBrowser(url);
163 });
164#endif
165 // Update the currentPostion to the first symbol after the
166 // Placeholder
167
169 processedArgument.PlaceholderPosition,
170 argument->Placeholder.Length());
171
172 if (currentPosition >= translated.Length())
173 break;
174 }
175
176 if (currentPosition < translated.Length())
177 S.AddFixedText(Verbatim(translated.substr(currentPosition)));
178 }
179 S.EndWrapLay();
180 }
181 S.EndHorizontalLay();
182
183 S.SetBorder(borderSize);
184}
185
186std::vector<AccessibleLinksFormatter::ProcessedArgument>
187AccessibleLinksFormatter::ProcessArguments(wxString translatedMessage) const
188{
189 std::vector<ProcessedArgument> result;
190 result.reserve(mFormatArguments.size());
191 // Arguments with the same placeholder are processed left-to-right.
192 // Lets track the last known position of the placeholder
193 std::unordered_map<wxString, size_t> knownPlaceholderPosition;
194
195 for (const FormatArgument& argument : mFormatArguments)
196 {
197 auto it = knownPlaceholderPosition.find(argument.Placeholder);
198
199 const size_t startingPosition =
200 it != knownPlaceholderPosition.end() ?
201 OffsetPosition(it->second, argument.Placeholder.length()) :
202 0;
203
204 const size_t placeholderPosition =
205 startingPosition == wxString::npos ?
207 translatedMessage.find(argument.Placeholder, startingPosition);
208
209 knownPlaceholderPosition[argument.Placeholder] = placeholderPosition;
210
211 if (placeholderPosition != wxString::npos)
212 {
213 result.emplace_back(
214 ProcessedArgument { &argument, placeholderPosition });
215 }
216 }
217
218 std::sort(
219 result.begin(), result.end(),
220 [](const ProcessedArgument& lhs, const ProcessedArgument& rhs) {
221 return lhs.PlaceholderPosition < rhs.PlaceholderPosition;
222 });
223
224 return result;
225}
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
#define safenew
Definition: MemoryX.h:10
size_t currentPosition
#define S(N)
Definition: ToChars.cpp:64
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
constexpr size_t npos(-1)
STL namespace.