20#include <wx/stattext.h>
21#include <wx/textctrl.h>
41#if wxUSE_ACCESSIBILITY
74 using namespace std::chrono;
76 const auto time_passed =
77 system_clock::now() - system_clock::from_time_t(time);
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",
84 duration_cast<minutes>(time_passed).count()))
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()))
91 return wxDateTime(
static_cast<time_t
>(time)).Format();
112 void SetValue(
int row,
int col,
const wxString& value)
override
124 static const wxString colLabels[] = {
125 XO(
"Project Name").Translation(),
126 XO(
"Modified").Translation(),
129 return col < 2 ? colLabels[col] : wxString {};
139 static const int colWidths[] = { 400, 150 };
140 return col < 2 ? colWidths[col] : 0;
143 void Refresh(
int page,
const wxString& searchTerm)
145 using namespace std::chrono_literals;
149 switch (authResult.Result)
156 XO(
"Failed to authorize account"), {},
169 XO(
"Loading projects list..."));
176 while (std::future_status::ready != future.wait_for(100ms))
180 cancellationContext->Cancel();
183 auto result = future.get();
187 wxGridTableMessage msg(
188 this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0,
mResponse.
Items.size());
190 GetView()->ProcessTableMessage(msg);
193 if (std::holds_alternative<PaginatedProjectsResponse>(result))
195 auto response = std::get_if<PaginatedProjectsResponse>(&result);
200 wxGridTableMessage msg(
204 GetView()->ProcessTableMessage(msg);
211 auto responseResult = std::get_if<ResponseResult>(&result);
215 XO(
"Failed to get projects list"), {},
262 if (selectedRow.empty())
272 if (selectedRow.empty())
287#if wxUSE_ACCESSIBILITY
295 , mProjectsData { data }
299 void SetSelectedRow(
int rowId)
301 if (mLastId != InvalidRow)
304 wxACC_EVENT_OBJECT_SELECTIONREMOVE, mOwner.GetGridWindow(),
305 wxOBJID_CLIENT, mLastId);
311 wxACC_EVENT_OBJECT_FOCUS, mOwner.GetGridWindow(), wxOBJID_CLIENT,
316 wxACC_EVENT_OBJECT_SELECTION, mOwner.GetGridWindow(), wxOBJID_CLIENT,
322 void TableDataUpdated()
325 wxACC_EVENT_OBJECT_REORDER, mOwner.GetGridWindow(), wxOBJID_CLIENT, 0);
329 wxAccStatus GetChild(
int childId, wxAccessible** child)
override
331 if (childId == wxACC_SELF)
339 wxAccStatus GetChildCount(
int* childCount)
override
341 *childCount = mProjectsData.GetRowsCount();
346 GetDefaultAction(
int WXUNUSED(childId), wxString* actionName)
override
354 GetDescription(
int WXUNUSED(childId), wxString* description)
override
356 description->clear();
361 wxAccStatus GetHelpText(
int WXUNUSED(childId), wxString* helpText)
override
370 GetKeyboardShortcut(
int WXUNUSED(childId), wxString* shortcut)
override
376 wxAccStatus GetLocation(wxRect& rect,
int elementId)
override
378 if (elementId == wxACC_SELF)
380 rect = mOwner.GetRect();
382 mOwner.GetParent()->ClientToScreen(rect.GetPosition()));
386 const auto row = elementId - 1;
388 if (row > mProjectsData.GetRowsCount())
393 for (
int col = 0; col < mProjectsData.GetColsCount(); ++col)
394 rowRect.Union(mOwner.CellToRect(elementId - 1, col));
397 mOwner.CalcScrolledPosition(rowRect.GetPosition()));
399 mOwner.GetGridWindow()->ClientToScreen(rowRect.GetPosition()));
407 wxAccStatus GetName(
int childId, wxString*
name)
override
409 if (childId == wxACC_SELF)
412 const auto row = childId - 1;
414 if (row > mProjectsData.GetRowsCount())
417 for (
int col = 0; col < mProjectsData.GetColsCount(); ++col)
422 *
name += mProjectsData.GetColLabelValue(col) +
" " +
423 mProjectsData.GetValue(row, col);
429 wxAccStatus GetParent(wxAccessible**)
override
431 return wxACC_NOT_IMPLEMENTED;
434 wxAccStatus GetRole(
int childId, wxAccRole* role)
override
436 if (childId == wxACC_SELF)
438# if defined(__WXMSW__)
439 *role = wxROLE_SYSTEM_TABLE;
442# if defined(__WXMAC__)
443 *role = wxROLE_SYSTEM_GROUPING;
448 *role = wxROLE_SYSTEM_TEXT;
454 wxAccStatus GetSelections(wxVariant*)
override
456 return wxACC_NOT_IMPLEMENTED;
459 wxAccStatus GetState(
int childId,
long* state)
override
461 int flag = wxACC_STATE_SYSTEM_FOCUSABLE | wxACC_STATE_SYSTEM_SELECTABLE;
463 if (childId == wxACC_SELF)
469# if defined(__WXMSW__)
470 flag |= wxACC_STATE_SYSTEM_FOCUSED | wxACC_STATE_SYSTEM_SELECTED |
471 wxACC_STATE_SYSTEM_UNAVAILABLE | wxACC_STATE_SYSTEM_FOCUSED;
474# if defined(__WXMAC__)
475 flag |= wxACC_STATE_SYSTEM_UNAVAILABLE;
477 if (childId == mLastId)
478 flag |= wxACC_STATE_SYSTEM_SELECTED | wxACC_STATE_SYSTEM_FOCUSED;
486 wxAccStatus GetValue(
int childId, wxString* strValue)
override
490# if defined(__WXMSW__)
492# elif defined(__WXMAC__)
493 return GetName(childId, strValue);
495 return wxACC_NOT_IMPLEMENTED;
499# if defined(__WXMAC__)
500 wxAccStatus Select(
int childId, wxAccSelectionFlags selectFlags)
override
502 if (childId == wxACC_SELF)
505 if (selectFlags & wxACC_SEL_TAKESELECTION)
506 mOwner.SetGridCursor(childId - 1, 0);
509 childId - 1, 0, childId - 1, 0, selectFlags & wxACC_SEL_ADDSELECTION);
515 wxAccStatus GetFocus(
int* childId, wxAccessible** child)
override
519 if (mProjectsData.GetRowsCount() == 0)
529 ProjectsTableData& mProjectsData;
531 static constexpr int InvalidRow = -1;
532 int mLastId { InvalidRow };
543 safenew wxStaticText {
this, wxID_ANY,
544 XO(
"Cloud saved projects").Translation() };
546 safenew wxStaticText {
this, wxID_ANY,
XO(
"Search:").Translation() };
549 wxEmptyString, wxDefaultPosition,
550 wxDefaultSize, wxTE_PROCESS_ENTER };
562 mProjectsTable->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTER);
575#if wxUSE_ACCESSIBILITY
583 safenew wxButton {
this, wxID_ANY,
XO(
"Prev").Translation() };
585 safenew wxButton {
this, wxID_ANY,
XO(
"Next").Translation() };
588 XO(
"View in audio.com").Translation() };
590 auto topSizer =
safenew wxBoxSizer { wxVERTICAL };
592 auto headerSizer =
safenew wxBoxSizer { wxHORIZONTAL };
593 headerSizer->Add(header, wxSizerFlags().CenterVertical().Left());
594 headerSizer->AddStretchSpacer();
596 searchHeader, wxSizerFlags().CenterVertical().Border(wxRIGHT, 4));
597 headerSizer->Add(
mSearchCtrl, wxSizerFlags().CenterVertical());
599 topSizer->Add(headerSizer, wxSizerFlags().Expand().Border(wxALL, 16));
601 mProjectsTable, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT, 16));
603 auto pageSizer =
safenew wxBoxSizer { wxHORIZONTAL };
604 pageSizer->Add(
mPageLabel, wxSizerFlags().CenterVertical());
605 pageSizer->AddStretchSpacer();
608 topSizer->AddSpacer(8);
610 pageSizer, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT, 16));
612 auto buttonsSizer =
safenew wxBoxSizer { wxHORIZONTAL };
613 buttonsSizer->Add(
mOpenAudioCom, wxSizerFlags().CenterVertical());
614 buttonsSizer->AddStretchSpacer();
615 buttonsSizer->Add(
mOpenButton, wxSizerFlags().CenterVertical());
617 topSizer->Add(buttonsSizer, wxSizerFlags().Expand().Border(wxALL, 16));
648 wxEVT_GRID_CELL_LEFT_DCLICK, [
this](
auto&) {
OnOpen(); });
654 if (!IsEscapeKey(evt))
660 EndModal(wxID_CANCEL);
664 wxEVT_GRID_RANGE_SELECT, [
this](
auto& evt) {
OnGridSelect(evt); });
667 wxEVT_GRID_SELECT_CELL, [
this](
auto& evt) {
OnSelectCell(evt); });
673 const auto keyCode = evt.GetKeyCode();
674 if (keyCode != WXK_RETURN && keyCode != WXK_NUMPAD_ENTER)
687 const auto keyCode = evt.GetKeyCode();
689 if (keyCode == WXK_UP &&
mProjectsTable->GetGridCursorRow() == 0) {
693 if (keyCode == WXK_DOWN &&
698 if (keyCode != WXK_RETURN && keyCode != WXK_NUMPAD_ENTER)
710 NavigateIn(evt.ShiftDown() ? wxNavigationKeyEvent::IsBackward :
711 wxNavigationKeyEvent::IsForward);
720#if wxUSE_ACCESSIBILITY
723 mAccessible->SetSelectedRow(row);
754#if wxUSE_ACCESSIBILITY
755 mAccessible->TableDataUpdated();
781 if (projectInfo ==
nullptr)
784 if (projectInfo->HeadSnapshot.Synced == 0)
790 const bool hasValidSnapshot =
791 !projectInfo->LastSyncedSnapshotId.empty();
813 { OpenProjectFromCloud(project, selectedProjectId, {},
false); });
816void ProjectsListDialog::OnOpenAudioCom()
818 if (mProjectsTable->GetSelectedRows().empty())
821 const auto selectedProjectUrl = mProjectsTableData->GetSelectedProjectUrl();
823 if (selectedProjectUrl.empty())
829void ProjectsListDialog::OnGridSelect(wxGridRangeSelectEvent& event)
833 if (!event.Selecting())
835 mOpenButton->Disable();
836 mOpenAudioCom->Disable();
840 mOpenButton->Enable();
841 mOpenAudioCom->Enable();
843 const auto topRow =
event.GetTopRow();
844 const auto bottomRow =
event.GetBottomRow();
845 const auto currentRow = mProjectsTable->GetGridCursorRow();
847 if (topRow != bottomRow)
849 if (mInRangeSelection)
852 mInRangeSelection =
true;
853 auto switcher =
finally([
this] { mInRangeSelection =
false; });
855 mProjectsTable->SelectRow(currentRow == topRow ? bottomRow : topRow);
859void ProjectsListDialog::OnSelectCell(wxGridEvent& event)
862 mProjectsTable->SelectRow(event.GetRow());
864#if wxUSE_ACCESSIBILITY
865 mAccessible->SetSelectedRow(event.GetRow());
869void ProjectsListDialog::OnSearchTextChanged()
871 mSearchTimer->StartOnce(500);
874void ProjectsListDialog::OnSearchTextSubmitted()
876 if (mSearchTimer->IsRunning())
877 mSearchTimer->Stop();
879 const auto searchTerm = mSearchCtrl->GetValue();
881 if (searchTerm == mLastSearchValue)
884 mLastSearchValue = searchTerm;
886 mProjectsTableData->Refresh(1, mLastSearchValue);
Toolkit-neutral facade for basic user interface services.
Declare functions to perform UTF-8 to std::wstring conversions.
const TranslatableString name
#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)
std::string GetProjectPageUrl(std::string_view userId, std::string_view projectId) const
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)
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, 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.
ErrorDialogOptions && Log(std::wstring log_) &&
std::vector< ProjectInfo > Items
PaginationInfo Pagination