20#include <wx/stattext.h>
21#include <wx/textctrl.h>
44#if wxUSE_ACCESSIBILITY
77 using namespace std::chrono;
79 const auto time_passed =
80 system_clock::now() - system_clock::from_time_t(time);
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",
87 duration_cast<minutes>(time_passed).count()))
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()))
94 return wxDateTime(
static_cast<time_t
>(time)).Format();
115 void SetValue(
int row,
int col,
const wxString& value)
override
127 static const wxString colLabels[] = {
128 XO(
"Project Name").Translation(),
129 XO(
"Modified").Translation(),
132 return col < 2 ? colLabels[col] : wxString {};
142 static const int colWidths[] = { 400, 150 };
143 return col < 2 ? colWidths[col] : 0;
146 void Refresh(
int page,
const wxString& searchTerm)
148 using namespace std::chrono_literals;
153 switch (authResult.Result)
160 XO(
"Failed to authorize account"), {},
173 XO(
"Loading projects list..."));
180 while (std::future_status::ready != future.wait_for(100ms))
184 cancellationContext->Cancel();
187 auto result = future.get();
191 wxGridTableMessage msg(
192 this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0,
mResponse.
Items.size());
194 GetView()->ProcessTableMessage(msg);
197 if (std::holds_alternative<PaginatedProjectsResponse>(result))
199 auto response = std::get_if<PaginatedProjectsResponse>(&result);
204 wxGridTableMessage msg(
208 GetView()->ProcessTableMessage(msg);
215 auto responseResult = std::get_if<ResponseResult>(&result);
219 XO(
"Failed to get projects list"), {},
266 if (selectedRow.empty())
276 if (selectedRow.empty())
287 auto url = oauthService.MakeAudioComAuthorizeURL(
userId, projectPage);
299#if wxUSE_ACCESSIBILITY
307 , mProjectsData { data }
311 void SetSelectedRow(
int rowId)
313 if (mLastId != InvalidRow)
316 wxACC_EVENT_OBJECT_SELECTIONREMOVE, mOwner.GetGridWindow(),
317 wxOBJID_CLIENT, mLastId);
323 wxACC_EVENT_OBJECT_FOCUS, mOwner.GetGridWindow(), wxOBJID_CLIENT,
328 wxACC_EVENT_OBJECT_SELECTION, mOwner.GetGridWindow(), wxOBJID_CLIENT,
334 void TableDataUpdated()
337 wxACC_EVENT_OBJECT_REORDER, mOwner.GetGridWindow(), wxOBJID_CLIENT, 0);
341 wxAccStatus GetChild(
int childId, wxAccessible** child)
override
343 if (childId == wxACC_SELF)
351 wxAccStatus GetChildCount(
int* childCount)
override
353 *childCount = mProjectsData.GetRowsCount();
358 GetDefaultAction(
int WXUNUSED(childId), wxString* actionName)
override
366 GetDescription(
int WXUNUSED(childId), wxString* description)
override
368 description->clear();
373 wxAccStatus GetHelpText(
int WXUNUSED(childId), wxString* helpText)
override
382 GetKeyboardShortcut(
int WXUNUSED(childId), wxString* shortcut)
override
388 wxAccStatus GetLocation(wxRect& rect,
int elementId)
override
390 if (elementId == wxACC_SELF)
392 rect = mOwner.GetRect();
394 mOwner.GetParent()->ClientToScreen(rect.GetPosition()));
398 const auto row = elementId - 1;
400 if (row > mProjectsData.GetRowsCount())
405 for (
int col = 0; col < mProjectsData.GetColsCount(); ++col)
406 rowRect.Union(mOwner.CellToRect(elementId - 1, col));
409 mOwner.CalcScrolledPosition(rowRect.GetPosition()));
411 mOwner.GetGridWindow()->ClientToScreen(rowRect.GetPosition()));
419 wxAccStatus GetName(
int childId, wxString*
name)
override
421 if (childId == wxACC_SELF)
424 const auto row = childId - 1;
426 if (row > mProjectsData.GetRowsCount())
429 for (
int col = 0; col < mProjectsData.GetColsCount(); ++col)
434 *
name += mProjectsData.GetColLabelValue(col) +
" " +
435 mProjectsData.GetValue(row, col);
441 wxAccStatus GetParent(wxAccessible**)
override
443 return wxACC_NOT_IMPLEMENTED;
446 wxAccStatus GetRole(
int childId, wxAccRole* role)
override
448 if (childId == wxACC_SELF)
450# if defined(__WXMSW__)
451 *role = wxROLE_SYSTEM_TABLE;
454# if defined(__WXMAC__)
455 *role = wxROLE_SYSTEM_GROUPING;
460 *role = wxROLE_SYSTEM_TEXT;
466 wxAccStatus GetSelections(wxVariant*)
override
468 return wxACC_NOT_IMPLEMENTED;
471 wxAccStatus GetState(
int childId,
long* state)
override
473 int flag = wxACC_STATE_SYSTEM_FOCUSABLE | wxACC_STATE_SYSTEM_SELECTABLE;
475 if (childId == wxACC_SELF)
481# if defined(__WXMSW__)
482 flag |= wxACC_STATE_SYSTEM_FOCUSED | wxACC_STATE_SYSTEM_SELECTED |
483 wxACC_STATE_SYSTEM_UNAVAILABLE | wxACC_STATE_SYSTEM_FOCUSED;
486# if defined(__WXMAC__)
487 flag |= wxACC_STATE_SYSTEM_UNAVAILABLE;
489 if (childId == mLastId)
490 flag |= wxACC_STATE_SYSTEM_SELECTED | wxACC_STATE_SYSTEM_FOCUSED;
498 wxAccStatus GetValue(
int childId, wxString* strValue)
override
502# if defined(__WXMSW__)
504# elif defined(__WXMAC__)
505 return GetName(childId, strValue);
507 return wxACC_NOT_IMPLEMENTED;
511# if defined(__WXMAC__)
512 wxAccStatus Select(
int childId, wxAccSelectionFlags selectFlags)
override
514 if (childId == wxACC_SELF)
517 if (selectFlags & wxACC_SEL_TAKESELECTION)
518 mOwner.SetGridCursor(childId - 1, 0);
521 childId - 1, 0, childId - 1, 0, selectFlags & wxACC_SEL_ADDSELECTION);
527 wxAccStatus GetFocus(
int* childId, wxAccessible** child)
override
531 if (mProjectsData.GetRowsCount() == 0)
541 ProjectsTableData& mProjectsData;
543 static constexpr int InvalidRow = -1;
544 int mLastId { InvalidRow };
555 safenew wxStaticText {
this, wxID_ANY,
556 XO(
"Cloud saved projects").Translation() };
558 safenew wxStaticText {
this, wxID_ANY,
XO(
"Search:").Translation() };
561 wxEmptyString, wxDefaultPosition,
562 wxDefaultSize, wxTE_PROCESS_ENTER };
574 mProjectsTable->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTER);
587#if wxUSE_ACCESSIBILITY
595 safenew wxButton {
this, wxID_ANY,
XO(
"Prev").Translation() };
597 safenew wxButton {
this, wxID_ANY,
XO(
"Next").Translation() };
600 XO(
"View in audio.com").Translation() };
602 auto topSizer =
safenew wxBoxSizer { wxVERTICAL };
604 auto headerSizer =
safenew wxBoxSizer { wxHORIZONTAL };
605 headerSizer->Add(header, wxSizerFlags().CenterVertical().Left());
606 headerSizer->AddStretchSpacer();
608 searchHeader, wxSizerFlags().CenterVertical().Border(wxRIGHT, 4));
609 headerSizer->Add(
mSearchCtrl, wxSizerFlags().CenterVertical());
611 topSizer->Add(headerSizer, wxSizerFlags().Expand().Border(wxALL, 16));
613 mProjectsTable, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT, 16));
615 auto pageSizer =
safenew wxBoxSizer { wxHORIZONTAL };
616 pageSizer->Add(
mPageLabel, wxSizerFlags().CenterVertical());
617 pageSizer->AddStretchSpacer();
620 topSizer->AddSpacer(8);
622 pageSizer, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT, 16));
624 auto buttonsSizer =
safenew wxBoxSizer { wxHORIZONTAL };
625 buttonsSizer->Add(
mOpenAudioCom, wxSizerFlags().CenterVertical());
626 buttonsSizer->AddStretchSpacer();
627 buttonsSizer->Add(
mOpenButton, wxSizerFlags().CenterVertical());
629 topSizer->Add(buttonsSizer, wxSizerFlags().Expand().Border(wxALL, 16));
660 wxEVT_GRID_CELL_LEFT_DCLICK, [
this](
auto&) {
OnOpen(); });
666 if (!IsEscapeKey(evt))
672 EndModal(wxID_CANCEL);
676 wxEVT_GRID_RANGE_SELECT, [
this](
auto& evt) {
OnGridSelect(evt); });
679 wxEVT_GRID_SELECT_CELL, [
this](
auto& evt) {
OnSelectCell(evt); });
685 const auto keyCode = evt.GetKeyCode();
686 if (keyCode != WXK_RETURN && keyCode != WXK_NUMPAD_ENTER)
699 const auto keyCode = evt.GetKeyCode();
701 if (keyCode == WXK_UP &&
mProjectsTable->GetGridCursorRow() == 0) {
705 if (keyCode == WXK_DOWN &&
710 if (keyCode != WXK_RETURN && keyCode != WXK_NUMPAD_ENTER)
722 NavigateIn(evt.ShiftDown() ? wxNavigationKeyEvent::IsBackward :
723 wxNavigationKeyEvent::IsForward);
732#if wxUSE_ACCESSIBILITY
735 mAccessible->SetSelectedRow(row);
766#if wxUSE_ACCESSIBILITY
767 mAccessible->TableDataUpdated();
793 if (projectInfo ==
nullptr)
796 if (projectInfo->HeadSnapshot.Synced == 0)
802 const bool hasValidSnapshot =
803 !projectInfo->LastSyncedSnapshotId.empty();
825 { OpenProjectFromCloud(project, selectedProjectId, {},
false); });
828void ProjectsListDialog::OnOpenAudioCom()
830 if (mProjectsTable->GetSelectedRows().empty())
833 const auto selectedProjectUrl = mProjectsTableData->GetSelectedProjectUrl();
835 if (selectedProjectUrl.empty())
841void ProjectsListDialog::OnGridSelect(wxGridRangeSelectEvent& event)
845 if (!event.Selecting())
847 mOpenButton->Disable();
848 mOpenAudioCom->Disable();
852 mOpenButton->Enable();
853 mOpenAudioCom->Enable();
855 const auto topRow =
event.GetTopRow();
856 const auto bottomRow =
event.GetBottomRow();
857 const auto currentRow = mProjectsTable->GetGridCursorRow();
859 if (topRow != bottomRow)
861 if (mInRangeSelection)
864 mInRangeSelection =
true;
865 auto switcher =
finally([
this] { mInRangeSelection =
false; });
867 mProjectsTable->SelectRow(currentRow == topRow ? bottomRow : topRow);
871void ProjectsListDialog::OnSelectCell(wxGridEvent& event)
874 mProjectsTable->SelectRow(event.GetRow());
876#if wxUSE_ACCESSIBILITY
877 mAccessible->SetSelectedRow(event.GetRow());
881void ProjectsListDialog::OnSearchTextChanged()
883 mSearchTimer->StartOnce(500);
886void ProjectsListDialog::OnSearchTextSubmitted()
888 if (mSearchTimer->IsRunning())
889 mSearchTimer->Stop();
891 const auto searchTerm = mSearchCtrl->GetValue();
893 if (searchTerm == mLastSearchValue)
896 mLastSearchValue = searchTerm;
898 mProjectsTableData->Refresh(1, mLastSearchValue);
Toolkit-neutral facade for basic user interface services.
Declare functions to perform UTF-8 to std::wstring conversions.
#define XP(sing, plur, n)
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
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 CloudSyncService & Get()
static ProjectState GetProjectState(const std::string &projectId)
static DialogButtonIdentifier CancelButtonIdentifier()
ProjectsListDialog & mOwner
wxString GetRowLabelValue(int row) override
wxString GetValue(int row, int col) override
const ProjectInfo * GetSelectedProjectInfo() const
std::string GetSelectedProjectUrl() const
int GetNumberCols() override
int GetCurrentPage() const
wxString GetCornerLabelValue() const override
int GetColWidth(int col) const
int GetPagesCount() const
void Refresh(int page, const wxString &searchTerm)
wxString GetColLabelValue(int col) override
static wxString FormatTime(int64_t time)
void SetValue(int row, int col, const wxString &value) override
ProjectsTableData(ProjectsListDialog &owner, int pageSize)
PaginatedProjectsResponse mResponse
int GetNumberRows() override
AudacityProject * mProject
ProjectsListDialog(wxWindow *parent, AudacityProject *project)
void OnSearchTextSubmitted()
void OnGridSelect(wxGridRangeSelectEvent &event)
wxButton * mNextPageButton
void OnSearchTextChanged()
void OnRefreshCompleted(bool success)
~ProjectsListDialog() override
std::unique_ptr< wxTimer > mSearchTimer
void OnSelectCell(wxGridEvent &event)
wxStaticText * mPageLabel
wxButton * mPrevPageButton
ProjectsTableData * mProjectsTableData
wxString mLastSearchValue
static DialogButtonIdentifier VisitAudioComButtonIdentifier()
static CancellationContextPtr Create()
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)
bool OpenInDefaultBrowser(const wxString &url)
Open an URL in default browser.
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
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.
void Yield()
Dispatch waiting events, including actions enqueued by CallAfter.
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
const auto OpenFromCloudTitle
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.
ErrorDialogOptions && Log(std::wstring log_) &&
std::vector< ProjectInfo > Items
PaginationInfo Pagination