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