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