#include "../Audacity.h" #include "../Experimental.h" #include #include #include #include "../AboutDialog.h" #include "../AllThemeResources.h" #include "../AudacityLogger.h" #include "../AudioIOBase.h" #include "../CommonCommandFlags.h" #include "../CrashReport.h" #include "../Dependencies.h" #include "../FileNames.h" #include "../HelpText.h" #include "../Menus.h" #include "../Prefs.h" #include "../Project.h" #include "../ProjectSelectionManager.h" #include "../ShuttleGui.h" #include "../SplashDialog.h" #include "../Theme.h" #include "../commands/CommandContext.h" #include "../commands/CommandManager.h" #include "../prefs/PrefsDialog.h" #include "../widgets/AudacityMessageBox.h" #include "../widgets/HelpSystem.h" #if defined(EXPERIMENTAL_CRASH_REPORT) #include #endif // private helper classes and functions namespace { void ShowDiagnostics( AudacityProject &project, const wxString &info, const TranslatableString &description, const wxString &defaultPath, bool fixedWidth = false) { auto &window = GetProjectFrame( project ); wxDialogWrapper dlg( &window, wxID_ANY, description); dlg.SetName(); ShuttleGui S(&dlg, eIsCreating); wxTextCtrl *text; S.StartVerticalLay(); { text = S.Id(wxID_STATIC) .Style(wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH) .AddTextWindow(""); S.AddStandardButtons(eOkButton | eCancelButton); } S.EndVerticalLay(); if (fixedWidth) { auto style = text->GetDefaultStyle(); style.SetFontFamily( wxFONTFAMILY_TELETYPE ); text->SetDefaultStyle(style); } *text << info; dlg.FindWindowById(wxID_OK)->SetLabel(_("&Save")); dlg.SetSize(350, 450); if (dlg.ShowModal() == wxID_OK) { const auto fileDialogTitle = XO("Save %s").Format( description ); wxString fName = FileNames::SelectFile(FileNames::Operation::Export, fileDialogTitle, wxEmptyString, defaultPath, wxT("txt"), { FileNames::TextFiles }, wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER, &window); if (!fName.empty()) { if (!text->SaveFile(fName)) { AudacityMessageBox( XO("Unable to save %s").Format( description ), fileDialogTitle); } } } } /** @brief Class which makes a dialog for displaying quick fixes to common issues. * * This class originated with the 'Stuck in a mode' problem, where far too many * users get into a mode without realising, and don't know how to get out. * It is a band-aid, and we should do more towards a full and proper solution * where there are fewer special modes, and they don't persisit. */ class QuickFixDialog : public wxDialogWrapper { public: QuickFixDialog(wxWindow * pParent, AudacityProject &project); void Populate(); void PopulateOrExchange(ShuttleGui & S); void AddStuck( ShuttleGui & S, bool & bBool, wxString Pref, const TranslatableString &Prompt, wxString Help ); void OnOk(wxCommandEvent &event); void OnCancel(wxCommandEvent &event); void OnHelp(wxCommandEvent &event); void OnFix(wxCommandEvent &event); wxString StringFromEvent( wxCommandEvent &event ); AudacityProject &mProject; int mItem; bool mbSyncLocked; bool mbInSnapTo; bool mbSoundActivated; DECLARE_EVENT_TABLE() }; #define FixButtonID 7001 #define HelpButtonID 7011 #define FakeButtonID 7021 BEGIN_EVENT_TABLE(QuickFixDialog, wxDialogWrapper) EVT_BUTTON(wxID_OK, QuickFixDialog::OnOk) EVT_BUTTON(wxID_CANCEL, QuickFixDialog::OnCancel) EVT_BUTTON(wxID_HELP, QuickFixDialog::OnHelp) EVT_COMMAND_RANGE(FixButtonID, HelpButtonID-1, wxEVT_BUTTON, QuickFixDialog::OnFix) EVT_COMMAND_RANGE(HelpButtonID, FakeButtonID-1, wxEVT_BUTTON, QuickFixDialog::OnHelp) END_EVENT_TABLE(); QuickFixDialog::QuickFixDialog(wxWindow * pParent, AudacityProject &project) : wxDialogWrapper(pParent, wxID_ANY, XO("Do you have these problems?"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE ) , mProject{ project } { const long SNAP_OFF = 0; gPrefs->Read(wxT("/GUI/SyncLockTracks"), &mbSyncLocked, false); mbInSnapTo = gPrefs->Read(wxT("/SnapTo"), SNAP_OFF) !=0; gPrefs->Read(wxT("/AudioIO/SoundActivatedRecord"), &mbSoundActivated, false); ShuttleGui S(this, eIsCreating); PopulateOrExchange(S); Fit(); auto sz = GetSize(); SetMinSize( sz ); SetMaxSize( sz ); // The close button has the cancel id and acts exactly the same as cancel. wxButton * pWin = (wxButton*)FindWindowById( wxID_CANCEL ); if( pWin ) pWin->SetFocus( ); Center(); } void QuickFixDialog::AddStuck( ShuttleGui & S, bool & bBool, wxString Pref, const TranslatableString &Prompt, wxString Help ) { mItem++; if( !bBool) return; S.AddFixedText( Prompt ); S.Id(FixButtonID + mItem).AddButton( XO("Fix") )->SetClientObject( safenew wxStringClientData(Pref)); { // Replace standard Help button with smaller icon button. // bs->AddButton(safenew wxButton(parent, wxID_HELP)); auto b = safenew wxBitmapButton(S.GetParent(), HelpButtonID+mItem, theTheme.Bitmap( bmpHelpIcon )); b->SetToolTip( _("Help") ); b->SetLabel(_("Help")); // for screen readers b->SetClientObject( safenew wxStringClientData( Help )); S.AddWindow( b ); } } void QuickFixDialog::PopulateOrExchange(ShuttleGui & S) { S.StartVerticalLay(1); S.StartStatic( XO("Quick Fixes")); // These aren't all possible modes one can be stuck in, but they are some of them. bool bStuckInMode = mbSyncLocked || mbInSnapTo || mbSoundActivated; if( !bStuckInMode ){ SetLabel(XO("Nothing to do")); S.AddFixedText(XO("No quick, easily fixed problems were found")); } else { S.StartMultiColumn(3, wxALIGN_CENTER); { mItem = -1; // Use # in the URLs to ensure we go to the online version of help. // Local help may well not be installed. AddStuck( S, mbSyncLocked, "/GUI/SyncLockTracks", XO("Clocks on the Tracks"), "Quick_Fix#sync_lock" ); AddStuck( S, mbInSnapTo, "/SnapTo", XO("Can't select precisely"), "Quick_Fix#snap_to" ); AddStuck( S, mbSoundActivated, "/AudioIO/SoundActivatedRecord", XO("Recording stops and starts"), "Quick_Fix#sound_activated_recording" ); } S.EndMultiColumn(); } S.EndStatic(); S.StartHorizontalLay(wxALIGN_CENTER_HORIZONTAL, 0); S.AddStandardButtons(eCloseButton + (bStuckInMode ? 0 : eHelpButton)); S.EndHorizontalLay(); S.EndVerticalLay(); wxButton * pBtn = (wxButton*)FindWindowById( wxID_HELP ); if( pBtn ) pBtn->SetClientObject( safenew wxStringClientData( "Quick_Fix#" )); } void QuickFixDialog::OnOk(wxCommandEvent &event) { (void)event;// Compiler food EndModal(wxID_OK); } void QuickFixDialog::OnCancel(wxCommandEvent &event) { (void)event;// Compiler food EndModal(wxID_CANCEL); } wxString QuickFixDialog::StringFromEvent( wxCommandEvent &event ) { wxButton * pBtn = (wxButton*)event.GetEventObject(); if( !pBtn ){ wxFAIL_MSG( "Event Object not found"); return ""; } wxStringClientData * pStrCd = (wxStringClientData*)(pBtn->GetClientObject()); if( !pStrCd ){ wxFAIL_MSG( "Client Data not found"); return ""; } wxString Str = pStrCd->GetData(); if( Str.empty()){ wxFAIL_MSG( "String data empty"); return ""; } return Str; } void QuickFixDialog::OnHelp(wxCommandEvent &event) { HelpSystem::ShowHelp(this, StringFromEvent( event ), true); } void QuickFixDialog::OnFix(wxCommandEvent &event) { wxString Str = StringFromEvent( event ); gPrefs->Write( Str, 0); gPrefs->Flush(); { // Sadly SnapTo has to be handled specially, as it is not part of the standard // preference dialogs. if( Str == "/SnapTo" ) { ProjectSelectionManager::Get( mProject ).AS_SetSnapTo( 0 ); } else { // This is overkill (aka slow), as all preferences are reloaded and all // toolbars recreated. // Overkill probably doesn't matter, as this command is infrequently used. DoReloadPreferences( mProject ); } } // Change the label after doing the fix, as the fix may take a second or two. wxButton * pBtn = (wxButton*)event.GetEventObject(); if( pBtn ) pBtn->SetLabel( _("Fixed") ); // The close button has the cancel id and acts exactly the same as cancel. wxButton * pWin = (wxButton*)FindWindowById( wxID_CANCEL ); if( pWin ) pWin->SetFocus( ); } } namespace HelpActions { // exported helper functions // Menu handler functions struct Handler : CommandHandlerObject { void OnQuickFix(const CommandContext &context) { auto &project = context.project; QuickFixDialog dlg( &GetProjectFrame( project ), project ); dlg.ShowModal(); } void OnQuickHelp(const CommandContext &context) { auto &project = context.project; HelpSystem::ShowHelp( &GetProjectFrame( project ), wxT("Quick_Help")); } void OnManual(const CommandContext &context) { auto &project = context.project; HelpSystem::ShowHelp( &GetProjectFrame( project ), wxT("Main_Page")); } void OnAudioDeviceInfo(const CommandContext &context) { auto &project = context.project; auto gAudioIO = AudioIOBase::Get(); wxString info = gAudioIO->GetDeviceInfo(); ShowDiagnostics( project, info, XO("Audio Device Info"), wxT("deviceinfo.txt") ); } #ifdef EXPERIMENTAL_MIDI_OUT void OnMidiDeviceInfo(const CommandContext &context) { auto &project = context.project; auto gAudioIO = AudioIOBase::Get(); wxString info = gAudioIO->GetMidiDeviceInfo(); ShowDiagnostics( project, info, XO("MIDI Device Info"), wxT("midideviceinfo.txt") ); } #endif void OnShowLog( const CommandContext &context ) { auto logger = AudacityLogger::Get(); if (logger) { logger->Show(); } } #if defined(EXPERIMENTAL_CRASH_REPORT) void OnCrashReport(const CommandContext &WXUNUSED(context) ) { // Change to "1" to test a real crash #if 0 char *p = 0; *p = 1234; #endif CrashReport::Generate(wxDebugReport::Context_Current); } #endif void OnCheckDependencies(const CommandContext &context) { auto &project = context.project; ::ShowDependencyDialogIfNeeded(&project, false); } void OnMenuTree(const CommandContext &context) { auto &project = context.project; using namespace MenuTable; struct MyVisitor : MenuVisitor { using MenuVisitor::MenuVisitor; enum : unsigned { TAB = 3 }; void BeginGroup( GroupItem &item, const Path& ) override { auto pItem = &item; if ( pItem->Transparent() ) { } else if ( const auto pGroup = dynamic_cast( pItem ) ) { if ( !needSeparator.empty() ) needSeparator.back() = true; } else { MaybeEmitSeparator(); Indent(); // using GET for alpha only diagnostic tool info += item.name.GET(); Return(); indentation = wxString{ ' ', TAB * ++level }; needSeparator.push_back( false ); firstItem.push_back( true ); } } void EndGroup( GroupItem &item, const Path& ) override { auto pItem = &item; if ( pItem->Transparent() ) { } else if ( const auto pGroup = dynamic_cast( pItem ) ) { if ( !needSeparator.empty() ) needSeparator.back() = true; } else { firstItem.pop_back(); needSeparator.pop_back(); indentation = wxString{ ' ', TAB * --level }; } } void Visit( SingleItem &item, const Path& ) override { MaybeEmitSeparator(); // using GET for alpha only diagnostic tool Indent(); info += item.name.GET(); Return(); } void MaybeEmitSeparator() { static const wxString separatorName{ '=', 20 }; bool separate = false; if ( !needSeparator.empty() ) { separate = needSeparator.back() && !firstItem.back(); needSeparator.back() = false; firstItem.back() = false; } if ( separate ) { Indent(); info += separatorName; Return(); } } void Indent() { info += indentation; } void Return() { info += '\n'; } unsigned level{}; wxString indentation; wxString info; std::vector firstItem; std::vector needSeparator; } visitor{ project }; MenuManager::Visit( visitor ); ShowDiagnostics( project, visitor.info, XO("Menu Tree"), wxT("menutree.txt"), true ); } void OnCheckForUpdates(const CommandContext &WXUNUSED(context)) { ::OpenInDefaultBrowser( VerCheckUrl()); } void OnAbout(const CommandContext &context) { #ifdef __WXMAC__ // Modeless dialog, consistent with other Mac applications // Simulate the application Exit menu item wxCommandEvent evt{ wxEVT_MENU, wxID_ABOUT }; wxTheApp->AddPendingEvent( evt ); #else auto &project = context.project; auto &window = GetProjectFrame( project ); // Windows and Linux still modal. AboutDialog dlog( &window ); dlog.ShowModal(); #endif } #if 0 // Legacy handlers, not used as of version 2.3.0 // Only does the update checks if it's an ALPHA build and not disabled by // preferences. void MayCheckForUpdates(AudacityProject &project) { #ifdef IS_ALPHA OnCheckForUpdates(project); #endif } void OnHelpWelcome(const CommandContext &context) { SplashDialog::DoHelpWelcome( context.project ); } #endif }; // struct Handler } // namespace static CommandHandlerObject &findCommandHandler(AudacityProject &) { // Handler is not stateful. Doesn't need a factory registered with // AudacityProject. static HelpActions::Handler instance; return instance; }; // Menu definitions #define FN(X) (& HelpActions::Handler :: X) namespace { using namespace MenuTable; BaseItemSharedPtr HelpMenu() { static BaseItemSharedPtr menu{ ( FinderScope{ findCommandHandler }, Menu( wxT("Help"), XO("&Help"), Section( "Basic", // QuickFix menu item not in Audacity 2.3.1 whilst we discuss further. #ifdef EXPERIMENTAL_DA // DA: Has QuickFix menu item. Command( wxT("QuickFix"), XXO("&Quick Fix..."), FN(OnQuickFix), AlwaysEnabledFlag ), // DA: 'Getting Started' rather than 'Quick Help'. Command( wxT("QuickHelp"), XXO("&Getting Started"), FN(OnQuickHelp) ), // DA: Emphasise it is the Audacity Manual (No separate DA manual). Command( wxT("Manual"), XXO("Audacity &Manual"), FN(OnManual) ) #else Command( wxT("QuickHelp"), XXO("&Quick Help..."), FN(OnQuickHelp), AlwaysEnabledFlag ), Command( wxT("Manual"), XXO("&Manual..."), FN(OnManual), AlwaysEnabledFlag ) #endif ), #ifdef __WXMAC__ Items #else Section #endif ( "Other", Menu( wxT("Diagnostics"), XO("&Diagnostics"), Command( wxT("DeviceInfo"), XXO("Au&dio Device Info..."), FN(OnAudioDeviceInfo), AudioIONotBusyFlag() ), #ifdef EXPERIMENTAL_MIDI_OUT Command( wxT("MidiDeviceInfo"), XXO("&MIDI Device Info..."), FN(OnMidiDeviceInfo), AudioIONotBusyFlag() ), #endif Command( wxT("Log"), XXO("Show &Log..."), FN(OnShowLog), AlwaysEnabledFlag ), #if defined(EXPERIMENTAL_CRASH_REPORT) Command( wxT("CrashReport"), XXO("&Generate Support Data..."), FN(OnCrashReport), AlwaysEnabledFlag ), #endif Command( wxT("CheckDeps"), XXO("Chec&k Dependencies..."), FN(OnCheckDependencies), AudioIONotBusyFlag() ) #ifdef IS_ALPHA , // Menu explorer. Perhaps this should become a macro command Command( wxT("MenuTree"), XXO("Menu Tree..."), FN(OnMenuTree), AlwaysEnabledFlag ) #endif ) #ifndef __WXMAC__ ), Section( "", #else , #endif // DA: Does not fully support update checking. #ifndef EXPERIMENTAL_DA Command( wxT("Updates"), XXO("&Check for Updates..."), FN(OnCheckForUpdates), AlwaysEnabledFlag ), #endif Command( wxT("About"), XXO("&About Audacity..."), FN(OnAbout), AlwaysEnabledFlag ) ) ) ) }; return menu; } AttachedItem sAttachment1{ wxT(""), Shared( HelpMenu() ) }; } #undef FN