Audacity 3.2.0
CloudSyncStatusField.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 CloudSyncStatusField.cpp
7
8 Dmitry Vedenko
9
10**********************************************************************/
11
13
14#include <algorithm>
15
16#include <wx/bitmap.h>
17#include <wx/dcbuffer.h>
18#include <wx/graphics.h>
19#include <wx/statusbr.h>
20
21#include "Project.h"
22#include "ProjectStatus.h"
23#include "ProjectWindow.h"
25
26#include "AllThemeResources.h"
27#include "Theme.h"
28#include "wxPanelWrapper.h"
29
30#include "Prefs.h"
31
32#if wxUSE_ACCESSIBILITY
33# include "WindowAccessible.h"
34#endif
35
37{
38namespace
39{
40const StatusBarField FieldId { L"CloudSyncStatus" };
41
44 { return std::make_shared<CloudSyncStatusField>(project); }
45};
46
48{
49public:
52 {
53 }
54
55 int GetDefaultWidth(const AudacityProject& project) const override
56 {
58 }
59
61 {
62 const auto index =
64
65 if (index < 0)
66 return;
67
68 wxRect rect;
69 if (ProjectWindow::Get(project).GetStatusBar()->GetFieldRect(index, rect))
71 }
72
73 void
75 {
76 }
77
79 {
81 }
82
83 bool IsVisible(const AudacityProject& project) const override
84 {
86 }
87
89 {
90 DispatchFieldChanged(project);
91 }
92}; // class CloudSyncStatusBarFieldItem
93
95 std::make_unique<CloudSyncStatusBarFieldItem>(),
97};
98
99const auto CloudSyncFailedMessage = XO("Failed.");
100const auto CloudSyncProgressMessage = XO("Syncing to audio.com... (%d%%)");
101const auto Padding = 2;
102const auto ProgressBarWidth = 240;
103const auto ProgressBarHeight = 10;
105
106#if __WXMAC__
107const auto StatusFieldPadding = 20;
108#else
109const auto StatusFieldPadding = 0;
110#endif
111
112} // namespace
113
115 public wxPanelWrapper,
116 public PrefsListener
117{
118public:
119 StatusWidget(CloudSyncStatusField& owner, wxWindow* parent)
120 : wxPanelWrapper { parent }
121 , mOwner { owner }
122 {
123 SetBackgroundStyle(wxBG_STYLE_PAINT);
124 UpdatePrefs();
125
126 Bind(wxEVT_PAINT, [this](auto&) { OnPaint(); });
127
128#if wxUSE_ACCESSIBILITY
129 SetAccessible(safenew WindowAccessible(this));
130#endif
131 }
132
133 ~StatusWidget() override
134 {
135 }
136
137 void SetRect(const wxRect& rect)
138 {
139 SetSize(rect);
140 }
141
142 int GetPreferredWidth(State state) const
143 {
144 switch (state)
145 {
146 case State::Failed:
147 return mSyncedBitmap->GetWidth() + mCloudSyncFailedMessageWidth +
148 Padding * 4;
149 case State::Uploading:
152 }
153
154 return mSyncedBitmap->GetWidth() + Padding * 2;
155 }
156
157 const wxBitmap* GetBitmap() const
158 {
159 return mOwner.mState == State::Uploading ? mProgressBitmap :
161 }
162
164 {
165 if (mOwner.mState == State::Uploading)
168 else if (mOwner.mState == State::Failed)
170
171 return {};
172 }
173
174 wxString GetText() const
175 {
177 }
178
179 void OnPaint()
180 {
181 wxAutoBufferedPaintDC dc(this);
182 std::unique_ptr<wxGraphicsContext> gc(wxGraphicsContext::Create(dc));
183
184 auto bitmap = GetBitmap();
185
186 const wxSize widgetSize = GetSize();
187 const wxSize bitmapSize = bitmap->GetSize();
188
189 gc->SetBrush(wxBrush(GetBackgroundColour()));
190 gc->DrawRectangle(0, 0, widgetSize.x, widgetSize.y);
191 gc->DrawBitmap(
192 *bitmap, Padding, (widgetSize.y - bitmapSize.y) / 2.0, bitmapSize.x,
193 bitmapSize.y);
194
195 const auto text = GetText();
196
197 if (text.empty())
198 return;
199
200 gc->SetFont(GetFont(), GetForegroundColour());
201 gc->DrawText(text, Padding + bitmapSize.x + 2 * Padding, 0);
202
203 if (mOwner.mState != State::Uploading)
204 return;
205
206 gc->SetAntialiasMode(wxANTIALIAS_NONE);
207
208 const auto progress = std::clamp(mOwner.mProgress, 0, 100);
209
210 const auto progressFilledPen =
211 gc->CreatePen(wxGraphicsPenInfo {}
212 .Colour(wxColour(0xc3c3c3))
213 .Width(ProgressBarBorderSize));
214
215 const auto progressEmptyPen =
216 gc->CreatePen(wxGraphicsPenInfo {}
217 .Colour(wxColour(0xc3c3c3))
218 .Width(ProgressBarBorderSize));
219 const auto zeroPen = gc->CreatePen(
220 wxGraphicsPenInfo {}.Width(0).Style(wxPENSTYLE_TRANSPARENT));
221
222 const auto progressFilledBrush = gc->CreateBrush(wxColour(0x3cf03c));
223 const auto progressEmptyBrush = gc->CreateBrush(wxColour(0xffffff));
224
225 const auto progressBarBorderLeft =
226 Padding + bitmapSize.x + 2 * Padding + mCloudSyncProgressMessageWidth;
227
228 const auto progressBarBorderRight =
229 progressBarBorderLeft + ProgressBarWidth;
230
231 const auto progressBarBorderTop =
232 (widgetSize.y - ProgressBarHeight) / 2.0;
233
234 const auto progressBarBorderBottom =
235 progressBarBorderTop + ProgressBarHeight;
236
237 const auto filledWidth =
238 (ProgressBarWidth - ProgressBarBorderSize * 2) * progress / 100;
239
240 const auto progressBarFillLeft =
241 progressBarBorderLeft + ProgressBarBorderSize;
242 const auto progressBarFillRight = progressBarFillLeft + filledWidth;
243
244 const auto progressBarEmptyLeft =
245 progressBarFillRight + (progress > 0 ? 1 : 0);
246 const auto progressBarEmptyRight =
247 progressBarBorderRight - ProgressBarBorderSize;
248
249 const auto filledHeight = ProgressBarHeight - ProgressBarBorderSize;
250
251 // Draw border
252 if (progress == 0)
253 gc->SetPen(progressEmptyPen);
254 else
255 gc->SetPen(progressFilledPen);
256
257 gc->StrokeLine(
258 progressBarBorderLeft, progressBarBorderTop, progressBarBorderLeft,
259 progressBarBorderBottom);
260
261 if (progress > 0)
262 {
263 gc->StrokeLine(
264 progressBarFillLeft, progressBarBorderTop, progressBarFillRight,
265 progressBarBorderTop);
266
267 gc->StrokeLine(
268 progressBarFillLeft, progressBarBorderBottom, progressBarFillRight,
269 progressBarBorderBottom);
270
271 gc->SetPen(zeroPen);
272 gc->SetBrush(progressFilledBrush);
273
274 gc->DrawRectangle(
275 progressBarFillLeft, progressBarBorderTop + ProgressBarBorderSize,
276 progressBarFillRight - progressBarFillLeft + 1, filledHeight);
277 }
278
279 if (progress < 100)
280 {
281 gc->SetPen(progressEmptyPen);
282
283 gc->StrokeLine(
284 progressBarEmptyLeft, progressBarBorderTop, progressBarEmptyRight,
285 progressBarBorderTop);
286
287 gc->StrokeLine(
288 progressBarEmptyLeft, progressBarBorderBottom,
289 progressBarEmptyRight, progressBarBorderBottom);
290
291 gc->SetPen(zeroPen);
292 gc->SetBrush(progressEmptyBrush);
293
294 gc->DrawRectangle(
295 progressBarEmptyLeft, progressBarBorderTop + ProgressBarBorderSize,
296 progressBarEmptyRight - progressBarEmptyLeft + 1, filledHeight);
297 }
298
299 if (progress == 100)
300 gc->SetPen(progressFilledPen);
301 else
302 gc->SetPen(progressEmptyPen);
303
304 gc->StrokeLine(
305 progressBarBorderRight, progressBarBorderTop, progressBarBorderRight,
306 progressBarBorderBottom);
307 }
308
309 void UpdatePrefs() override
310 {
311 mSyncedBitmap = &theTheme.Bitmap(bmpCloud);
312 mProgressBitmap = &theTheme.Bitmap(bmpCloudProgress);
313
315 GetTextExtent(CloudSyncFailedMessage.Translation()).x;
316
319 .Format(100)
320 .Translation())
321 .x;
322 }
323
325 {
327 }
328
329private:
331
332 const wxBitmap* mSyncedBitmap {};
333 const wxBitmap* mProgressBitmap {};
334
337}; // class CloudSyncStatusField::StatusWidget
338
340 : mProject { project }
341 , mCloudExtension { ProjectCloudExtension::Get(project) }
342 , mCloudStatusChangedSubscription { mCloudExtension.SubscribeStatusChanged(
343 [this](const auto& extension) { OnCloudStatusChanged(extension); },
344 true) }
345{
346}
347
348CloudSyncStatusField::~CloudSyncStatusField() = default;
349
351{
352 return project.AttachedObjects::Get<CloudSyncStatusField&>(key);
353}
354
357{
358 return Get(const_cast<AudacityProject&>(project));
359}
360
361int CloudSyncStatusField::GetWidth() const
362{
363 return mCloudExtension.IsCloudProject() ?
364 (GetStatusWidget().GetPreferredWidth(mState) +
366 0;
367}
368
369void CloudSyncStatusField::OnSize(const wxRect& rect)
370{
371 GetStatusWidget().SetRect(rect);
372}
373
374bool CloudSyncStatusField::IsVisible() const
375{
376 return mState != State::Hidden;
377}
378
379TranslatableString CloudSyncStatusField::GetText() const
380{
381 return {};
382}
383
384void CloudSyncStatusField::MarkDirty()
385{
386 auto field = dynamic_cast<CloudSyncStatusBarFieldItem*>(
388
389 if (field)
390 field->MarkDirty(mProject);
391
392 auto& statusWidget = GetStatusWidget();
393
394 statusWidget.Show(mState != State::Hidden);
395 statusWidget.UpdateName();
396
397 if (statusWidget.GetParent())
398 statusWidget.GetParent()->Refresh();
399 else
400 statusWidget.Refresh();
401}
402
403void CloudSyncStatusField::OnCloudStatusChanged(
404 const CloudStatusChangedMessage& message)
405{
406 mState = [](ProjectSyncStatus status)
407 {
408 switch (status)
409 {
410 case ProjectSyncStatus::Local:
411 return State::Hidden;
412 case ProjectSyncStatus::Unsynced:
413 return State::Dirty;
414 case ProjectSyncStatus::Synced:
415 return State::Synced;
416 case ProjectSyncStatus::Failed:
417 return State::Failed;
418 case ProjectSyncStatus::Syncing:
419 return State::Uploading;
420 default:
421 return State::Hidden;
422 }
423 }(message.Status);
424
425 if (mState == State::Uploading)
426 mProgress = static_cast<int>(message.Progress * 100.0);
427
428 MarkDirty();
429}
430
431CloudSyncStatusField::StatusWidget& CloudSyncStatusField::GetStatusWidget()
432{
433 if (!mStatusWidget)
434 {
435 mStatusWidget = safenew StatusWidget(
436 *this, ProjectWindow::Get(mProject).GetStatusBar());
437
438 mStatusWidget->Show(mCloudExtension.IsCloudProject());
439 }
440
441 return *mStatusWidget;
442}
443
445CloudSyncStatusField::GetStatusWidget() const
446{
447 return const_cast<CloudSyncStatusField*>(this)->GetStatusWidget();
448}
449} // namespace audacity::cloud::audiocom::sync
struct State mState
XO("Cut/Copy/Paste")
#define field(n, t)
Definition: ImportAUP.cpp:165
#define safenew
Definition: MemoryX.h:10
StatusBarField RateStatusBarField()
ID of the third field in the status bar. This field is used to display the current rate.
wxString name
Definition: TagsEditor.cpp:166
const auto project
THEME_API Theme theTheme
Definition: Theme.cpp:82
static void OnSize(wxSizeEvent &evt)
Definition: VSTEditor.cpp:224
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:275
An explicitly nonlocalized string, not meant for the user to see.
Definition: Identifier.h:22
const wxString & GET() const
Explicit conversion to wxString, meant to be ugly-looking and demanding of a comment why it's correct...
Definition: Identifier.h:66
A listener notified of changes in preferences.
Definition: Prefs.h:652
static ProjectWindow & Get(AudacityProject &project)
Generates classes whose instances register items at construction.
Definition: Registry.h:388
Abstract base class for status bar fields.
Definition: ProjectStatus.h:38
wxBitmap & Bitmap(int iIndex)
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
void OnCloudStatusChanged(const CloudStatusChangedMessage &extension)
static CloudSyncStatusField & Get(AudacityProject &project)
TranslatableString GetText(const AudacityProject &project) const override
Retrieves the current text of the field.
bool IsVisible(const AudacityProject &project) const override
Returns true if the field is visible in the status bar of the given project.
void SetText(AudacityProject &project, const TranslatableString &msg) override
Sets the current text of the field.
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:202
static StatusBarFieldItem * Get(const StatusBarField &identifier)
Returns the field with the given identifier or nullptr if field is not present.
static int GetFieldIndex(const AudacityProject &project, const StatusBarField &identifier)
Returns the zero based index of the field or -1 if field is not present.
Definition: Dither.cpp:67