Audacity 3.2.0
ProjectsListDialog.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 ProjectsListDialog.cpp
7
8 Dmitry Vedenko
9
10**********************************************************************/
11
12#include "ProjectsListDialog.h"
13
14#include <cassert>
15#include <chrono>
16
17#include <wx/button.h>
18#include <wx/grid.h>
19#include <wx/sizer.h>
20#include <wx/stattext.h>
21#include <wx/textctrl.h>
22#include <wx/timer.h>
23
24#include "BasicUI.h"
25#include "CodeConversions.h"
26#include "Internat.h"
28
29#include "ProjectManager.h"
30
32#include "CloudSyncService.h"
33#include "ServiceConfig.h"
34
35#include "sync/CloudSyncDTO.h"
36
38
40
41#if wxUSE_ACCESSIBILITY
42# include "WindowAccessible.h"
43#endif
44
46{
47
48namespace
49{
50const auto OpenFromCloudTitle = XO("Open from Cloud");
51} // namespace
52
53class ProjectsListDialog::ProjectsTableData final : public wxGridTableBase
54{
55public:
57 : mOwner { owner }
58 , mPageSize { pageSize }
59 {
60 }
61
62 int GetNumberRows() override
63 {
64 return mResponse.Items.size();
65 }
66
67 int GetNumberCols() override
68 {
69 return 2;
70 }
71
72 static wxString FormatTime(int64_t time)
73 {
74 using namespace std::chrono;
75
76 const auto time_passed =
77 system_clock::now() - system_clock::from_time_t(time);
78
79 if (time_passed < minutes(1))
80 return XO("less than 1 minute").Translation();
81 if (time_passed < hours(1))
82 return XP("one minutes ago", "%d minutes ago",
83 0)(static_cast<int>(
84 duration_cast<minutes>(time_passed).count()))
85 .Translation();
86 if (time_passed < hours(48))
87 return XP("one hour ago", "%d hours ago", 0)(
88 static_cast<int>(duration_cast<hours>(time_passed).count()))
89 .Translation();
90
91 return wxDateTime(static_cast<time_t>(time)).Format();
92 }
93
94 wxString GetValue(int row, int col) override
95 {
96 if (row >= static_cast<int>(mResponse.Items.size()))
97 return {};
98
99 const auto item = mResponse.Items[row];
100
101 switch (col)
102 {
103 case 0:
104 return audacity::ToWXString(item.Name);
105 case 1:
106 return FormatTime(item.Updated);
107 }
108
109 return {};
110 }
111
112 void SetValue(int row, int col, const wxString& value) override
113 {
114 assert(false);
115 }
116
117 wxString GetRowLabelValue(int row) override
118 {
119 return {};
120 }
121
122 wxString GetColLabelValue(int col) override
123 {
124 static const wxString colLabels[] = {
125 XO("Project Name").Translation(),
126 XO("Modified").Translation(),
127 };
128
129 return col < 2 ? colLabels[col] : wxString {};
130 }
131
132 wxString GetCornerLabelValue() const override
133 {
134 return {};
135 }
136
137 int GetColWidth(int col) const
138 {
139 static const int colWidths[] = { 400, 150 };
140 return col < 2 ? colWidths[col] : 0;
141 }
142
143 void Refresh(int page, const wxString& searchTerm)
144 {
145 using namespace std::chrono_literals;
146
147 auto authResult = PerformBlockingAuth(mOwner.mProject);
148
149 switch (authResult.Result)
150 {
152 break;
156 XO("Failed to authorize account"), {},
158 audacity::ToWString(authResult.ErrorMessage)));
159 [[fallthrough]];
160 default:
161 mOwner.EndModal(0);
162 return;
163 }
164
166
167 auto progressDialog = BasicUI::MakeGenericProgress(
169 XO("Loading projects list..."));
170
171 auto cancellationContext = concurrency::CancellationContext::Create();
172
173 auto future = CloudSyncService::Get().GetProjects(
174 cancellationContext, page, mPageSize, ToUTF8(searchTerm));
175
176 while (std::future_status::ready != future.wait_for(100ms))
177 {
179 if (progressDialog->Pulse() != BasicUI::ProgressResult::Success)
180 cancellationContext->Cancel();
181 }
182
183 auto result = future.get();
184
185 if (!mResponse.Items.empty())
186 {
187 wxGridTableMessage msg(
188 this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, mResponse.Items.size());
189
190 GetView()->ProcessTableMessage(msg);
191 }
192
193 if (std::holds_alternative<PaginatedProjectsResponse>(result))
194 {
195 auto response = std::get_if<PaginatedProjectsResponse>(&result);
196 mResponse = std::move(*response);
197
198 if (!mResponse.Items.empty())
199 {
200 wxGridTableMessage msg(
201 this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, mResponse.Items.size(),
202 0);
203
204 GetView()->ProcessTableMessage(msg);
205 }
206
208 }
209 else
210 {
211 auto responseResult = std::get_if<ResponseResult>(&result);
212
215 XO("Failed to get projects list"), {},
217 audacity::ToWString(responseResult->Content)));
218
219 if (mResponse.Items.empty())
220 mOwner.EndModal(0);
221
223 }
224 }
225
226 bool HasPrevPage() const
227 {
229 }
230
231 bool HasNextPage() const
232 {
234 }
235
236 void PrevPage()
237 {
238 if (HasPrevPage())
240 }
241
242 void NextPage()
243 {
244 if (HasNextPage())
246 }
247
248 int GetCurrentPage() const
249 {
251 }
252
253 int GetPagesCount() const
254 {
256 }
257
259 {
260 const auto selectedRow = mOwner.mProjectsTable->GetSelectedRows();
261
262 if (selectedRow.empty())
263 return {};
264
265 return &mResponse.Items[selectedRow[0]];
266 }
267
268 std::string GetSelectedProjectUrl() const
269 {
270 const auto selectedRow = mOwner.mProjectsTable->GetSelectedRows();
271
272 if (selectedRow.empty())
273 return {};
274
275 auto& item = mResponse.Items[selectedRow[0]];
276
277 return GetServiceConfig().GetProjectPageUrl(item.Username, item.Id);
278 }
279
280private:
282 const int mPageSize;
283
285};
286
287#if wxUSE_ACCESSIBILITY
288
289class ProjectsListDialog::ProjectListAccessible : public WindowAccessible
290{
291public:
292 ProjectListAccessible(wxGrid& owner, ProjectsTableData& data)
293 : WindowAccessible { owner.GetGridWindow() }
294 , mOwner { owner }
295 , mProjectsData { data }
296 {
297 }
298
299 void SetSelectedRow(int rowId)
300 {
301 if (mLastId != InvalidRow)
302 {
303 NotifyEvent(
304 wxACC_EVENT_OBJECT_SELECTIONREMOVE, mOwner.GetGridWindow(),
305 wxOBJID_CLIENT, mLastId);
306 }
307
308 if (&mOwner == wxWindow::FindFocus())
309 {
310 NotifyEvent(
311 wxACC_EVENT_OBJECT_FOCUS, mOwner.GetGridWindow(), wxOBJID_CLIENT,
312 rowId + 1);
313 }
314
315 NotifyEvent(
316 wxACC_EVENT_OBJECT_SELECTION, mOwner.GetGridWindow(), wxOBJID_CLIENT,
317 rowId + 1);
318
319 mLastId = rowId + 1;
320 }
321
322 void TableDataUpdated()
323 {
324 NotifyEvent(
325 wxACC_EVENT_OBJECT_REORDER, mOwner.GetGridWindow(), wxOBJID_CLIENT, 0);
326 }
327
328private:
329 wxAccStatus GetChild(int childId, wxAccessible** child) override
330 {
331 if (childId == wxACC_SELF)
332 *child = this;
333 else
334 *child = nullptr;
335
336 return wxACC_OK;
337 }
338
339 wxAccStatus GetChildCount(int* childCount) override
340 {
341 *childCount = mProjectsData.GetRowsCount();
342 return wxACC_OK;
343 }
344
345 wxAccStatus
346 GetDefaultAction(int WXUNUSED(childId), wxString* actionName) override
347 {
348 actionName->clear();
349 return wxACC_OK;
350 }
351
352 // Returns the description for this object or a child.
353 wxAccStatus
354 GetDescription(int WXUNUSED(childId), wxString* description) override
355 {
356 description->clear();
357 return wxACC_OK;
358 }
359
360 // Returns help text for this object or a child, similar to tooltip text.
361 wxAccStatus GetHelpText(int WXUNUSED(childId), wxString* helpText) override
362 {
363 helpText->clear();
364 return wxACC_OK;
365 }
366
367 // Returns the keyboard shortcut for this object or child.
368 // Return e.g. ALT+K
369 wxAccStatus
370 GetKeyboardShortcut(int WXUNUSED(childId), wxString* shortcut) override
371 {
372 shortcut->clear();
373 return wxACC_OK;
374 }
375
376 wxAccStatus GetLocation(wxRect& rect, int elementId) override
377 {
378 if (elementId == wxACC_SELF)
379 {
380 rect = mOwner.GetRect();
381 rect.SetPosition(
382 mOwner.GetParent()->ClientToScreen(rect.GetPosition()));
383 }
384 else
385 {
386 const auto row = elementId - 1;
387
388 if (row > mProjectsData.GetRowsCount())
389 return wxACC_OK; // ?
390
391 wxRect rowRect;
392
393 for (int col = 0; col < mProjectsData.GetColsCount(); ++col)
394 rowRect.Union(mOwner.CellToRect(elementId - 1, col));
395
396 rowRect.SetPosition(
397 mOwner.CalcScrolledPosition(rowRect.GetPosition()));
398 rowRect.SetPosition(
399 mOwner.GetGridWindow()->ClientToScreen(rowRect.GetPosition()));
400
401 rect = rowRect;
402 }
403
404 return wxACC_OK;
405 }
406
407 wxAccStatus GetName(int childId, wxString* name) override
408 {
409 if (childId == wxACC_SELF)
410 return wxACC_OK;
411
412 const auto row = childId - 1;
413
414 if (row > mProjectsData.GetRowsCount())
415 return wxACC_OK; // ?
416
417 for (int col = 0; col < mProjectsData.GetColsCount(); ++col)
418 {
419 if (col != 0)
420 *name += ", ";
421
422 *name += mProjectsData.GetColLabelValue(col) + " " +
423 mProjectsData.GetValue(row, col);
424 }
425
426 return wxACC_OK;
427 }
428
429 wxAccStatus GetParent(wxAccessible**) override
430 {
431 return wxACC_NOT_IMPLEMENTED;
432 }
433
434 wxAccStatus GetRole(int childId, wxAccRole* role) override
435 {
436 if (childId == wxACC_SELF)
437 {
438# if defined(__WXMSW__)
439 *role = wxROLE_SYSTEM_TABLE;
440# endif
441
442# if defined(__WXMAC__)
443 *role = wxROLE_SYSTEM_GROUPING;
444# endif
445 }
446 else
447 {
448 *role = wxROLE_SYSTEM_TEXT;
449 }
450
451 return wxACC_OK;
452 }
453
454 wxAccStatus GetSelections(wxVariant*) override
455 {
456 return wxACC_NOT_IMPLEMENTED;
457 }
458
459 wxAccStatus GetState(int childId, long* state) override
460 {
461 int flag = wxACC_STATE_SYSTEM_FOCUSABLE | wxACC_STATE_SYSTEM_SELECTABLE;
462
463 if (childId == wxACC_SELF)
464 {
465 *state = 0;
466 return wxACC_FAIL;
467 }
468
469# if defined(__WXMSW__)
470 flag |= wxACC_STATE_SYSTEM_FOCUSED | wxACC_STATE_SYSTEM_SELECTED |
471 wxACC_STATE_SYSTEM_UNAVAILABLE | wxACC_STATE_SYSTEM_FOCUSED;
472# endif
473
474# if defined(__WXMAC__)
475 flag |= wxACC_STATE_SYSTEM_UNAVAILABLE;
476
477 if (childId == mLastId)
478 flag |= wxACC_STATE_SYSTEM_SELECTED | wxACC_STATE_SYSTEM_FOCUSED;
479# endif
480
481 *state = flag;
482
483 return wxACC_OK;
484 }
485
486 wxAccStatus GetValue(int childId, wxString* strValue) override
487 {
488 strValue->clear();
489
490# if defined(__WXMSW__)
491 return wxACC_OK;
492# elif defined(__WXMAC__)
493 return GetName(childId, strValue);
494# else
495 return wxACC_NOT_IMPLEMENTED;
496# endif
497 }
498
499# if defined(__WXMAC__)
500 wxAccStatus Select(int childId, wxAccSelectionFlags selectFlags) override
501 {
502 if (childId == wxACC_SELF)
503 return wxACC_OK;
504
505 if (selectFlags & wxACC_SEL_TAKESELECTION)
506 mOwner.SetGridCursor(childId - 1, 0);
507
508 mOwner.SelectBlock(
509 childId - 1, 0, childId - 1, 0, selectFlags & wxACC_SEL_ADDSELECTION);
510
511 return wxACC_OK;
512 }
513# endif
514
515 wxAccStatus GetFocus(int* childId, wxAccessible** child) override
516 {
517 if (&mOwner == wxWindow::FindFocus())
518 {
519 if (mProjectsData.GetRowsCount() == 0)
520 *child = this;
521 else
522 *childId = mLastId;
523 }
524
525 return wxACC_OK;
526 }
527
528 wxGrid& mOwner;
529 ProjectsTableData& mProjectsData;
530
531 static constexpr int InvalidRow = -1;
532 int mLastId { InvalidRow };
533}; // class ProjectListAccessible
534
535#endif
536
538 wxWindow* parent, AudacityProject* project)
539 : wxDialogWrapper { parent, wxID_ANY, OpenFromCloudTitle }
540 , mProject { project }
541{
542 auto header =
543 safenew wxStaticText { this, wxID_ANY,
544 XO("Cloud saved projects").Translation() };
545 auto searchHeader =
546 safenew wxStaticText { this, wxID_ANY, XO("Search:").Translation() };
547
548 mSearchCtrl = safenew wxTextCtrl { this, wxID_ANY,
549 wxEmptyString, wxDefaultPosition,
550 wxDefaultSize, wxTE_PROCESS_ENTER };
551
552 mProjectsTable = safenew wxGrid { this, wxID_ANY };
553
555
556 mProjectsTable->SetDefaultRowSize(32);
557
558 mProjectsTable->SetGridLineColour(
559 mProjectsTable->GetDefaultCellBackgroundColour());
560 mProjectsTable->SetCellHighlightPenWidth(0);
561
562 mProjectsTable->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTER);
563 mProjectsTable->SetTable(mProjectsTableData, true);
564 mProjectsTable->SetRowLabelSize(0);
565
566 mProjectsTable->EnableEditing(false);
567 mProjectsTable->SetSelectionMode(wxGrid::wxGridSelectRows);
568 mProjectsTable->SetTabBehaviour(wxGrid::Tab_Leave);
569
570 mProjectsTable->SetMinSize({ -1, 32 * 8 + 9 });
571
572 for (auto i = 0; i < mProjectsTableData->GetNumberCols(); ++i)
574
575#if wxUSE_ACCESSIBILITY
576 mAccessible =
577 safenew ProjectListAccessible { *mProjectsTable, *mProjectsTableData };
578 mProjectsTable->GetGridWindow()->SetAccessible(mAccessible);
579#endif
580
581 mPageLabel = safenew wxStaticText { this, wxID_ANY, {} };
583 safenew wxButton { this, wxID_ANY, XO("Prev").Translation() };
585 safenew wxButton { this, wxID_ANY, XO("Next").Translation() };
586 mOpenButton = safenew wxButton { this, wxID_ANY, XO("Open").Translation() };
587 mOpenAudioCom = safenew wxButton { this, wxID_ANY,
588 XO("View in audio.com").Translation() };
589
590 auto topSizer = safenew wxBoxSizer { wxVERTICAL };
591
592 auto headerSizer = safenew wxBoxSizer { wxHORIZONTAL };
593 headerSizer->Add(header, wxSizerFlags().CenterVertical().Left());
594 headerSizer->AddStretchSpacer();
595 headerSizer->Add(
596 searchHeader, wxSizerFlags().CenterVertical().Border(wxRIGHT, 4));
597 headerSizer->Add(mSearchCtrl, wxSizerFlags().CenterVertical());
598
599 topSizer->Add(headerSizer, wxSizerFlags().Expand().Border(wxALL, 16));
600 topSizer->Add(
601 mProjectsTable, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT, 16));
602
603 auto pageSizer = safenew wxBoxSizer { wxHORIZONTAL };
604 pageSizer->Add(mPageLabel, wxSizerFlags().CenterVertical());
605 pageSizer->AddStretchSpacer();
606 pageSizer->Add(mPrevPageButton, wxSizerFlags().CenterVertical());
607 pageSizer->Add(mNextPageButton, wxSizerFlags().CenterVertical());
608 topSizer->AddSpacer(8);
609 topSizer->Add(
610 pageSizer, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT, 16));
611
612 auto buttonsSizer = safenew wxBoxSizer { wxHORIZONTAL };
613 buttonsSizer->Add(mOpenAudioCom, wxSizerFlags().CenterVertical());
614 buttonsSizer->AddStretchSpacer();
615 buttonsSizer->Add(mOpenButton, wxSizerFlags().CenterVertical());
616
617 topSizer->Add(buttonsSizer, wxSizerFlags().Expand().Border(wxALL, 16));
618
619 mOpenButton->Disable();
620 mOpenAudioCom->Disable();
621
622 mSearchTimer = std::make_unique<wxTimer>(this);
623
624 SetSizer(topSizer);
625 Fit();
626 Center();
627
629 BasicUI::CallAfter([this]
631}
632
634
636{
637 mPrevPageButton->Bind(
638 wxEVT_BUTTON, [this](auto&) { mProjectsTableData->PrevPage(); });
639
640 mNextPageButton->Bind(
641 wxEVT_BUTTON, [this](auto&) { mProjectsTableData->NextPage(); });
642
643 mOpenButton->Bind(wxEVT_BUTTON, [this](auto&) { OnOpen(); });
644
645 mOpenAudioCom->Bind(wxEVT_BUTTON, [this](auto&) { OnOpenAudioCom(); });
646
647 mProjectsTable->Bind(
648 wxEVT_GRID_CELL_LEFT_DCLICK, [this](auto&) { OnOpen(); });
649
650 Bind(
651 wxEVT_CHAR_HOOK,
652 [this](auto& evt)
653 {
654 if (!IsEscapeKey(evt))
655 {
656 evt.Skip();
657 return;
658 }
659
660 EndModal(wxID_CANCEL);
661 });
662
663 mProjectsTable->Bind(
664 wxEVT_GRID_RANGE_SELECT, [this](auto& evt) { OnGridSelect(evt); });
665
666 mProjectsTable->Bind(
667 wxEVT_GRID_SELECT_CELL, [this](auto& evt) { OnSelectCell(evt); });
668
669 mProjectsTable->Bind(
670 wxEVT_KEY_UP,
671 [this](auto& evt)
672 {
673 const auto keyCode = evt.GetKeyCode();
674 if (keyCode != WXK_RETURN && keyCode != WXK_NUMPAD_ENTER)
675 {
676 evt.Skip();
677 return;
678 }
679
680 OnOpen();
681 });
682
683 mProjectsTable->Bind(
684 wxEVT_KEY_DOWN,
685 [this](auto& evt)
686 {
687 const auto keyCode = evt.GetKeyCode();
688 // prevent being able to up arrow past the first row (issue #6251)
689 if (keyCode == WXK_UP && mProjectsTable->GetGridCursorRow() == 0) {
690 return;
691 }
692 // prevent being able to down arrow past the last row (issue #6251)
693 if (keyCode == WXK_DOWN &&
694 mProjectsTable->GetGridCursorRow() ==
695 mProjectsTable->GetNumberRows() - 1) {
696 return;
697 }
698 if (keyCode != WXK_RETURN && keyCode != WXK_NUMPAD_ENTER)
699 {
700 evt.Skip();
701 return;
702 }
703 });
704
705 mProjectsTable->Bind(
706 wxEVT_GRID_TABBING,
707 [this](auto& evt)
708 {
709 // needed for correct tabbing - see issue #6190
710 NavigateIn(evt.ShiftDown() ? wxNavigationKeyEvent::IsBackward :
711 wxNavigationKeyEvent::IsForward);
712 });
713
714 mProjectsTable->Bind(
715 wxEVT_SET_FOCUS,
716 [this](auto& evt)
717 {
718 // needed so that for screen readers a row rather than the whole
719 // table is the initial focus - see issue #6190
720#if wxUSE_ACCESSIBILITY
721 int row = mProjectsTable->GetGridCursorRow();
722 if (row != -1)
723 mAccessible->SetSelectedRow(row);
724#endif
725 evt.Skip();
726 });
727
728 mSearchCtrl->Bind(wxEVT_TEXT, [this](auto&) { OnSearchTextChanged(); });
729
730 mSearchCtrl->Bind(
731 wxEVT_TEXT_ENTER, [this](auto&) { OnSearchTextSubmitted(); });
732
733 Bind(wxEVT_TIMER, [this](auto&) { OnSearchTextSubmitted(); });
734}
735
737{
738 mProjectsTable->Enable(false);
739 mPrevPageButton->Enable(false);
740 mNextPageButton->Enable(false);
741}
742
744{
745 mProjectsTable->Enable(success);
746
747 mPrevPageButton->Enable(success && mProjectsTableData->HasPrevPage());
748 mNextPageButton->Enable(success && mProjectsTableData->HasNextPage());
749
751
752 mProjectsTable->ForceRefresh();
753
754#if wxUSE_ACCESSIBILITY
755 mAccessible->TableDataUpdated();
756#endif
757}
758
760{
762 {
763 mPageLabel->SetLabel({});
764 return;
765 }
766
767 mPageLabel->SetLabel(XO("Page %d of %d")
768 .Format(
771 .Translation());
772}
773
775{
776 if (mProjectsTable->GetSelectedRows().empty())
777 return;
778
779 const auto projectInfo = mProjectsTableData->GetSelectedProjectInfo();
780
781 if (projectInfo == nullptr)
782 return;
783
784 if (projectInfo->HeadSnapshot.Synced == 0)
785 {
786 const auto state = CloudSyncService::GetProjectState(projectInfo->Id);
787
789 {
790 const bool hasValidSnapshot =
791 !projectInfo->LastSyncedSnapshotId.empty();
792
793 const auto result =
794 UnsyncedProjectDialog { mProject, hasValidSnapshot }.ShowDialog();
795
797 {
800
801 return;
802 }
803
805 return;
806 }
807 }
808
809 EndModal(wxID_OK);
810
812 [project = mProject, selectedProjectId = projectInfo->Id]
813 { OpenProjectFromCloud(project, selectedProjectId, {}, false); });
814}
815
816void ProjectsListDialog::OnOpenAudioCom()
817{
818 if (mProjectsTable->GetSelectedRows().empty())
819 return;
820
821 const auto selectedProjectUrl = mProjectsTableData->GetSelectedProjectUrl();
822
823 if (selectedProjectUrl.empty())
824 return;
825
826 BasicUI::OpenInDefaultBrowser(ToWXString(selectedProjectUrl));
827}
828
829void ProjectsListDialog::OnGridSelect(wxGridRangeSelectEvent& event)
830{
831 event.Skip();
832
833 if (!event.Selecting())
834 {
835 mOpenButton->Disable();
836 mOpenAudioCom->Disable();
837 return;
838 }
839
840 mOpenButton->Enable();
841 mOpenAudioCom->Enable();
842
843 const auto topRow = event.GetTopRow();
844 const auto bottomRow = event.GetBottomRow();
845 const auto currentRow = mProjectsTable->GetGridCursorRow();
846
847 if (topRow != bottomRow)
848 {
849 if (mInRangeSelection)
850 return;
851
852 mInRangeSelection = true;
853 auto switcher = finally([this] { mInRangeSelection = false; });
854
855 mProjectsTable->SelectRow(currentRow == topRow ? bottomRow : topRow);
856 }
857}
858
859void ProjectsListDialog::OnSelectCell(wxGridEvent& event)
860{
861 event.Skip();
862 mProjectsTable->SelectRow(event.GetRow());
863
864#if wxUSE_ACCESSIBILITY
865 mAccessible->SetSelectedRow(event.GetRow());
866#endif
867}
868
869void ProjectsListDialog::OnSearchTextChanged()
870{
871 mSearchTimer->StartOnce(500);
872}
873
874void ProjectsListDialog::OnSearchTextSubmitted()
875{
876 if (mSearchTimer->IsRunning())
877 mSearchTimer->Stop();
878
879 const auto searchTerm = mSearchCtrl->GetValue();
880
881 if (searchTerm == mLastSearchValue)
882 return;
883
884 mLastSearchValue = searchTerm;
885
886 mProjectsTableData->Refresh(1, mLastSearchValue);
887}
888
889} // namespace audacity::cloud::audiocom::sync
Toolkit-neutral facade for basic user interface services.
Declare functions to perform UTF-8 to std::wstring conversions.
const TranslatableString name
Definition: Distortion.cpp:76
XO("Cut/Copy/Paste")
#define XP(sing, plur, n)
Definition: Internat.h:94
#define safenew
Definition: MemoryX.h:10
const auto project
static std::once_flag flag
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
Abstract base class used in importing a file.
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
GetProjectsFuture GetProjects(concurrency::CancellationContextPtr context, int page, int pageSize, std::string_view searchString)
Retrieve the list of projects from the cloud.
static ProjectState GetProjectState(const std::string &projectId)
std::string GetProjectPageUrl(std::string_view userId, std::string_view projectId) const
void SetValue(int row, int col, const wxString &value) override
ProjectsListDialog(wxWindow *parent, AudacityProject *project)
std::unique_ptr< GenericProgressDialog > MakeGenericProgress(const WindowPlacement &placement, const TranslatableString &title, const TranslatableString &message)
Create and display a progress dialog (return nullptr if Services not installed)
Definition: BasicUI.h:312
bool OpenInDefaultBrowser(const wxString &url)
Open an URL in default browser.
Definition: BasicUI.cpp:246
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:214
void ShowErrorDialog(const WindowPlacement &placement, const TranslatableString &dlogTitle, const TranslatableString &message, const ManualPageID &helpPage, const ErrorDialogOptions &options={})
Show an error dialog with a link to the manual for further help.
Definition: BasicUI.h:264
void Yield()
Dispatch waiting events, including actions enqueued by CallAfter.
Definition: BasicUI.cpp:225
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:375
AuthResult PerformBlockingAuth(AudacityProject *project, const TranslatableString &alternativeActionLabel)
const ServiceConfig & GetServiceConfig()
Returns the instance of the ServiceConfig.
std::string ToUTF8(const std::wstring &wstr)
std::wstring ToWString(const std::string &str)
wxString ToWXString(const std::string &str)
Options for variations of error dialogs; the default is for modal dialogs.
Definition: BasicUI.h:52
ErrorDialogOptions && Log(std::wstring log_) &&
Definition: BasicUI.h:64
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.