20#include <wx/stattext.h>
21#include <wx/textctrl.h>
42#if wxUSE_ACCESSIBILITY
75 using namespace std::chrono;
77 const auto time_passed =
78 system_clock::now() - system_clock::from_time_t(time);
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",
85 duration_cast<minutes>(time_passed).count()))
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()))
92 return wxDateTime(
static_cast<time_t
>(time)).Format();
113 void SetValue(
int row,
int col,
const wxString& value)
override
125 static const wxString colLabels[] = {
126 XO(
"Project Name").Translation(),
127 XO(
"Modified").Translation(),
130 return col < 2 ? colLabels[col] : wxString {};
140 static const int colWidths[] = { 400, 150 };
141 return col < 2 ? colWidths[col] : 0;
144 void Refresh(
int page,
const wxString& searchTerm)
146 using namespace std::chrono_literals;
151 switch (authResult.Result)
158 XO(
"Failed to authorize account"), {},
171 XO(
"Loading projects list..."));
178 while (std::future_status::ready != future.wait_for(100ms))
182 cancellationContext->Cancel();
185 auto result = future.get();
189 wxGridTableMessage msg(
190 this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0,
mResponse.
Items.size());
192 GetView()->ProcessTableMessage(msg);
195 if (std::holds_alternative<PaginatedProjectsResponse>(result))
197 auto response = std::get_if<PaginatedProjectsResponse>(&result);
202 wxGridTableMessage msg(
206 GetView()->ProcessTableMessage(msg);
213 auto responseResult = std::get_if<ResponseResult>(&result);
217 XO(
"Failed to get projects list"), {},
264 if (selectedRow.empty())
274 if (selectedRow.empty())
290#if wxUSE_ACCESSIBILITY
298 , mProjectsData { data }
302 void SetSelectedRow(
int rowId)
304 if (mLastId != InvalidRow)
307 wxACC_EVENT_OBJECT_SELECTIONREMOVE, mOwner.GetGridWindow(),
308 wxOBJID_CLIENT, mLastId);
314 wxACC_EVENT_OBJECT_FOCUS, mOwner.GetGridWindow(), wxOBJID_CLIENT,
319 wxACC_EVENT_OBJECT_SELECTION, mOwner.GetGridWindow(), wxOBJID_CLIENT,
325 void TableDataUpdated()
328 wxACC_EVENT_OBJECT_REORDER, mOwner.GetGridWindow(), wxOBJID_CLIENT, 0);
332 wxAccStatus GetChild(
int childId, wxAccessible** child)
override
334 if (childId == wxACC_SELF)
342 wxAccStatus GetChildCount(
int* childCount)
override
344 *childCount = mProjectsData.GetRowsCount();
349 GetDefaultAction(
int WXUNUSED(childId), wxString* actionName)
override
357 GetDescription(
int WXUNUSED(childId), wxString* description)
override
359 description->clear();
364 wxAccStatus GetHelpText(
int WXUNUSED(childId), wxString* helpText)
override
373 GetKeyboardShortcut(
int WXUNUSED(childId), wxString* shortcut)
override
379 wxAccStatus GetLocation(wxRect& rect,
int elementId)
override
381 if (elementId == wxACC_SELF)
383 rect = mOwner.GetRect();
385 mOwner.GetParent()->ClientToScreen(rect.GetPosition()));
389 const auto row = elementId - 1;
391 if (row > mProjectsData.GetRowsCount())
396 for (
int col = 0; col < mProjectsData.GetColsCount(); ++col)
397 rowRect.Union(mOwner.CellToRect(elementId - 1, col));
400 mOwner.CalcScrolledPosition(rowRect.GetPosition()));
402 mOwner.GetGridWindow()->ClientToScreen(rowRect.GetPosition()));
410 wxAccStatus GetName(
int childId, wxString*
name)
override
412 if (childId == wxACC_SELF)
415 const auto row = childId - 1;
417 if (row > mProjectsData.GetRowsCount())
420 for (
int col = 0; col < mProjectsData.GetColsCount(); ++col)
425 *
name += mProjectsData.GetColLabelValue(col) +
" " +
426 mProjectsData.GetValue(row, col);
432 wxAccStatus GetParent(wxAccessible**)
override
434 return wxACC_NOT_IMPLEMENTED;
437 wxAccStatus GetRole(
int childId, wxAccRole* role)
override
439 if (childId == wxACC_SELF)
441# if defined(__WXMSW__)
442 *role = wxROLE_SYSTEM_TABLE;
445# if defined(__WXMAC__)
446 *role = wxROLE_SYSTEM_GROUPING;
451 *role = wxROLE_SYSTEM_TEXT;
457 wxAccStatus GetSelections(wxVariant*)
override
459 return wxACC_NOT_IMPLEMENTED;
462 wxAccStatus GetState(
int childId,
long* state)
override
464 int flag = wxACC_STATE_SYSTEM_FOCUSABLE | wxACC_STATE_SYSTEM_SELECTABLE;
466 if (childId == wxACC_SELF)
472# if defined(__WXMSW__)
473 flag |= wxACC_STATE_SYSTEM_FOCUSED | wxACC_STATE_SYSTEM_SELECTED |
474 wxACC_STATE_SYSTEM_UNAVAILABLE | wxACC_STATE_SYSTEM_FOCUSED;
477# if defined(__WXMAC__)
478 flag |= wxACC_STATE_SYSTEM_UNAVAILABLE;
480 if (childId == mLastId)
481 flag |= wxACC_STATE_SYSTEM_SELECTED | wxACC_STATE_SYSTEM_FOCUSED;
489 wxAccStatus GetValue(
int childId, wxString* strValue)
override
493# if defined(__WXMSW__)
495# elif defined(__WXMAC__)
496 return GetName(childId, strValue);
498 return wxACC_NOT_IMPLEMENTED;
502# if defined(__WXMAC__)
503 wxAccStatus Select(
int childId, wxAccSelectionFlags selectFlags)
override
505 if (childId == wxACC_SELF)
508 if (selectFlags & wxACC_SEL_TAKESELECTION)
509 mOwner.SetGridCursor(childId - 1, 0);
512 childId - 1, 0, childId - 1, 0, selectFlags & wxACC_SEL_ADDSELECTION);
518 wxAccStatus GetFocus(
int* childId, wxAccessible** child)
override
522 if (mProjectsData.GetRowsCount() == 0)
532 ProjectsTableData& mProjectsData;
534 static constexpr int InvalidRow = -1;
535 int mLastId { InvalidRow };
546 safenew wxStaticText {
this, wxID_ANY,
547 XO(
"Cloud saved projects").Translation() };
549 safenew wxStaticText {
this, wxID_ANY,
XO(
"Search:").Translation() };
552 wxEmptyString, wxDefaultPosition,
553 wxDefaultSize, wxTE_PROCESS_ENTER };
565 mProjectsTable->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTER);
578#if wxUSE_ACCESSIBILITY
586 safenew wxButton {
this, wxID_ANY,
XO(
"Prev").Translation() };
588 safenew wxButton {
this, wxID_ANY,
XO(
"Next").Translation() };
591 XO(
"View in audio.com").Translation() };
593 auto topSizer =
safenew wxBoxSizer { wxVERTICAL };
595 auto headerSizer =
safenew wxBoxSizer { wxHORIZONTAL };
596 headerSizer->Add(header, wxSizerFlags().CenterVertical().Left());
597 headerSizer->AddStretchSpacer();
599 searchHeader, wxSizerFlags().CenterVertical().Border(wxRIGHT, 4));
600 headerSizer->Add(
mSearchCtrl, wxSizerFlags().CenterVertical());
602 topSizer->Add(headerSizer, wxSizerFlags().Expand().Border(wxALL, 16));
604 mProjectsTable, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT, 16));
606 auto pageSizer =
safenew wxBoxSizer { wxHORIZONTAL };
607 pageSizer->Add(
mPageLabel, wxSizerFlags().CenterVertical());
608 pageSizer->AddStretchSpacer();
611 topSizer->AddSpacer(8);
613 pageSizer, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT, 16));
615 auto buttonsSizer =
safenew wxBoxSizer { wxHORIZONTAL };
616 buttonsSizer->Add(
mOpenAudioCom, wxSizerFlags().CenterVertical());
617 buttonsSizer->AddStretchSpacer();
618 buttonsSizer->Add(
mOpenButton, wxSizerFlags().CenterVertical());
620 topSizer->Add(buttonsSizer, wxSizerFlags().Expand().Border(wxALL, 16));
651 wxEVT_GRID_CELL_LEFT_DCLICK, [
this](
auto&) {
OnOpen(); });
657 if (!IsEscapeKey(evt))
663 EndModal(wxID_CANCEL);
667 wxEVT_GRID_RANGE_SELECT, [
this](
auto& evt) {
OnGridSelect(evt); });
670 wxEVT_GRID_SELECT_CELL, [
this](
auto& evt) {
OnSelectCell(evt); });
676 const auto keyCode = evt.GetKeyCode();
677 if (keyCode != WXK_RETURN && keyCode != WXK_NUMPAD_ENTER)
690 const auto keyCode = evt.GetKeyCode();
692 if (keyCode == WXK_UP &&
mProjectsTable->GetGridCursorRow() == 0) {
696 if (keyCode == WXK_DOWN &&
701 if (keyCode != WXK_RETURN && keyCode != WXK_NUMPAD_ENTER)
713 NavigateIn(evt.ShiftDown() ? wxNavigationKeyEvent::IsBackward :
714 wxNavigationKeyEvent::IsForward);
723#if wxUSE_ACCESSIBILITY
726 mAccessible->SetSelectedRow(row);
757#if wxUSE_ACCESSIBILITY
758 mAccessible->TableDataUpdated();
784 if (projectInfo ==
nullptr)
787 if (projectInfo->HeadSnapshot.Synced == 0)
793 const bool hasValidSnapshot =
794 !projectInfo->LastSyncedSnapshotId.empty();
816 { OpenProjectFromCloud(project, selectedProjectId, {},
false); });
819void ProjectsListDialog::OnOpenAudioCom()
821 if (mProjectsTable->GetSelectedRows().empty())
824 const auto selectedProjectUrl = mProjectsTableData->GetSelectedProjectUrl();
826 if (selectedProjectUrl.empty())
832void ProjectsListDialog::OnGridSelect(wxGridRangeSelectEvent& event)
836 if (!event.Selecting())
838 mOpenButton->Disable();
839 mOpenAudioCom->Disable();
843 mOpenButton->Enable();
844 mOpenAudioCom->Enable();
846 const auto topRow =
event.GetTopRow();
847 const auto bottomRow =
event.GetBottomRow();
848 const auto currentRow = mProjectsTable->GetGridCursorRow();
850 if (topRow != bottomRow)
852 if (mInRangeSelection)
855 mInRangeSelection =
true;
856 auto switcher =
finally([
this] { mInRangeSelection =
false; });
858 mProjectsTable->SelectRow(currentRow == topRow ? bottomRow : topRow);
862void ProjectsListDialog::OnSelectCell(wxGridEvent& event)
865 mProjectsTable->SelectRow(event.GetRow());
867#if wxUSE_ACCESSIBILITY
868 mAccessible->SetSelectedRow(event.GetRow());
872void ProjectsListDialog::OnSearchTextChanged()
874 mSearchTimer->StartOnce(500);
877void ProjectsListDialog::OnSearchTextSubmitted()
879 if (mSearchTimer->IsRunning())
880 mSearchTimer->Stop();
882 const auto searchTerm = mSearchCtrl->GetValue();
884 if (searchTerm == mLastSearchValue)
887 mLastSearchValue = searchTerm;
889 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, AudiocomTrace) 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, 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)
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