Audacity  3.0.3
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 <wx/hyperlink.h>
20 
21 #ifdef __WXGTK__
22 #include <wx/stattext.h>
23 #endif
24 
25 namespace
26 {
27 size_t OffsetPosition(size_t position, size_t length)
28 {
29  if (position == wxString::npos)
30  return wxString::npos;
31 
32  return position + length;
33 }
34 }
35 
37  : mMessage(std::move(message))
38 {
39 }
40 
42  wxString placeholder, TranslatableString value, std::string targetURL)
43 {
44  mFormatArguments.push_back({
45  std::move(placeholder),
46  std::move(value),
47  {},
48  std::move(targetURL)
49  });
50 
51  return *this;
52 }
53 
55  wxString placeholder, TranslatableString value,
56  LinkClickedHandler handler)
57 {
58  mFormatArguments.push_back({
59  std::move(placeholder),
60  std::move(value),
61  std::move(handler),
62  {}
63  });
64 
65  return *this;
66 }
67 
69 {
70  // Just add the text, if there are no links to format
71  if (mFormatArguments.empty())
72  {
74  return;
75  }
76 
77 #ifdef __WXGTK__
78  // Non empty label is required, as wxHyperlinkCtrl would assert otherwise
79  std::unique_ptr<wxHyperlinkCtrl> tempHyperlink
80  = std::make_unique<wxHyperlinkCtrl>(S.GetParent(), wxID_ANY, wxT("temp"), wxString());
81 
82  const wxColour hyperlinkColour = tempHyperlink->GetNormalColour();
83 
84  tempHyperlink.reset();
85 #endif
86 
87  wxString translated = mMessage.Translation();
88 
89  std::vector<ProcessedArgument> processedArguments =
90  ProcessArguments(translated);
91 
92  if (processedArguments.empty())
93  {
95  return;
96  }
97 
98  const int borderSize = S.GetBorder();
99 
100  S.StartHorizontalLay(wxEXPAND);
101  {
102  S.SetBorder(0);
103  S.AddSpace(borderSize);
104 
105  S.StartWrapLay(wxEXPAND, 1);
106  {
107  size_t currentPosition = 0;
108 
109  for (const ProcessedArgument& processedArgument : processedArguments)
110  {
111  const FormatArgument* argument = processedArgument.Argument;
112 
113  // Add everything between currentPosition and PlaceholderPosition
114 
115  if (currentPosition != processedArgument.PlaceholderPosition)
116  {
117  const size_t substrLength =
118  processedArgument.PlaceholderPosition - currentPosition;
119 
120  S.Prop(0).AddFixedText(
121  Verbatim(translated.substr(currentPosition, substrLength)));
122  }
123 
124  // Add hyperlink
125 #ifndef __WXGTK__
126  wxHyperlinkCtrl* hyperlink = safenew wxHyperlinkCtrl(
127  S.GetParent(), wxID_ANY, argument->Value.Translation(),
128  argument->TargetURL);
129 
130  if (argument->Handler)
131  {
132  hyperlink->Bind(
133  wxEVT_HYPERLINK, [handler = argument->Handler](wxHyperlinkEvent& evt) {
134  handler();
135  });
136  }
137 
138  S.AddWindow(hyperlink, wxALIGN_TOP | wxALIGN_LEFT);
139 #else
140  wxStaticText* hyperlink = S.AddVariableText(argument->Value);
141 
142  hyperlink->SetFont(hyperlink->GetFont().Underlined());
143  hyperlink->SetForegroundColour(hyperlinkColour);
144  hyperlink->SetCursor(wxCURSOR_HAND);
145 
146  hyperlink->Bind(wxEVT_LEFT_UP, [handler = argument->Handler, url = argument->TargetURL](wxEvent&) {
147  if (handler)
148  handler();
149  else if (!url.empty())
150  wxLaunchDefaultBrowser(url);
151  });
152 #endif
153  // Update the currentPostion to the first symbol after the
154  // Placeholder
155 
157  processedArgument.PlaceholderPosition,
158  argument->Placeholder.Length());
159 
160  if (currentPosition >= translated.Length())
161  break;
162  }
163 
164  if (currentPosition < translated.Length())
165  S.AddFixedText(Verbatim(translated.substr(currentPosition)));
166  }
167  S.EndWrapLay();
168  }
169  S.EndHorizontalLay();
170 
171  S.SetBorder(borderSize);
172 }
173 
174 std::vector<AccessibleLinksFormatter::ProcessedArgument>
175 AccessibleLinksFormatter::ProcessArguments(wxString translatedMessage) const
176 {
177  std::vector<ProcessedArgument> result;
178  result.reserve(mFormatArguments.size());
179  // Arguments with the same placeholder are processed left-to-right.
180  // Lets track the last known position of the placeholder
181  std::unordered_map<wxString, size_t> knownPlaceholderPosition;
182 
183  for (const FormatArgument& argument : mFormatArguments)
184  {
185  auto it = knownPlaceholderPosition.find(argument.Placeholder);
186 
187  const size_t startingPosition =
188  it != knownPlaceholderPosition.end() ?
189  OffsetPosition(it->second, argument.Placeholder.length()) :
190  0;
191 
192  const size_t placeholderPosition =
193  startingPosition == wxString::npos ?
194  wxString::npos :
195  translatedMessage.find(argument.Placeholder, startingPosition);
196 
197  knownPlaceholderPosition[argument.Placeholder] = placeholderPosition;
198 
199  if (placeholderPosition != wxString::npos)
200  {
201  result.emplace_back(
202  ProcessedArgument { &argument, placeholderPosition });
203  }
204  }
205 
206  std::sort(
207  result.begin(), result.end(),
208  [](const ProcessedArgument& lhs, const ProcessedArgument& rhs) {
209  return lhs.PlaceholderPosition < rhs.PlaceholderPosition;
210  });
211 
212  return result;
213 }
TranslatableString
Holds a msgid for the translation catalog; may also bind format arguments.
Definition: TranslatableString.h:32
currentPosition
size_t currentPosition
Definition: ScripterCallback.cpp:62
ShuttleGuiBase::EndWrapLay
void EndWrapLay()
Definition: ShuttleGui.cpp:1221
ShuttleGui::AddSpace
wxSizerItem * AddSpace(int width, int height, int prop=0)
Definition: ShuttleGui.cpp:2459
ShuttleGuiBase::GetBorder
int GetBorder() const noexcept
Definition: ShuttleGui.cpp:196
ShuttleGuiBase::EndHorizontalLay
void EndHorizontalLay()
Definition: ShuttleGui.cpp:1177
ShuttleGuiBase::StartHorizontalLay
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
Definition: ShuttleGui.cpp:1167
ShuttleGuiBase::AddFixedText
void AddFixedText(const TranslatableString &Str, bool bCenter=false, int wrapWidth=0)
Definition: ShuttleGui.cpp:440
ShuttleGuiBase::GetParent
wxWindow * GetParent()
Definition: ShuttleGui.h:496
ShuttleGuiBase::AddWindow
wxWindow * AddWindow(wxWindow *pWindow, int PositionFlags=wxALIGN_CENTRE)
Definition: ShuttleGui.cpp:299
ShuttleGui.h
ShuttleGui::Prop
ShuttleGui & Prop(int iProp)
Definition: ShuttleGui.h:725
Verbatim
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
Definition: TranslatableString.h:321
TranslatableString::Translation
wxString Translation() const
Definition: TranslatableString.h:79
ShuttleGuiBase::SetBorder
void SetBorder(int Border)
Definition: ShuttleGui.h:489
ShuttleGuiBase::AddVariableText
wxStaticText * AddVariableText(const TranslatableString &Str, bool bCenter=false, int PositionFlags=0, int wrapWidth=0)
Definition: ShuttleGui.cpp:463
safenew
#define safenew
Definition: MemoryX.h:10
ShuttleGuiBase::StartWrapLay
void StartWrapLay(int PositionFlags=wxEXPAND, int iProp=0)
Definition: ShuttleGui.cpp:1210
ShuttleGui
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:631