mirror of
				https://github.com/cookiengineer/audacity
				synced 2025-10-31 14:13:50 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1013 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1013 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /**********************************************************************
 | |
| 
 | |
|   Audacity: A Digital Audio Editor
 | |
| 
 | |
|   PrefsDialog.cpp
 | |
| 
 | |
|   Joshua Haberman
 | |
|   James Crook
 | |
| 
 | |
| *******************************************************************//**
 | |
| 
 | |
| \class PrefsDialog
 | |
| \brief Dialog that shows the current PrefsPanel in a tabbed divider.
 | |
| 
 | |
| *//*******************************************************************/
 | |
| 
 | |
| #include "../Audacity.h" // for USE_* macros
 | |
| #include "PrefsDialog.h"
 | |
| 
 | |
| #include <mutex> // for std::call_once
 | |
| 
 | |
| #include <wx/app.h>
 | |
| #include <wx/setup.h> // for wxUSE_* macros
 | |
| #include <wx/defs.h>
 | |
| #include <wx/button.h>
 | |
| #include <wx/dialog.h>
 | |
| #include <wx/event.h>
 | |
| #include <wx/font.h>
 | |
| #include <wx/gdicmn.h>
 | |
| #include <wx/intl.h>
 | |
| #include <wx/listbox.h>
 | |
| #include <wx/sizer.h>
 | |
| 
 | |
| #include <wx/listbook.h>
 | |
| 
 | |
| #include <wx/treebook.h>
 | |
| #include <wx/treectrl.h>
 | |
| 
 | |
| #include "../AudioIOBase.h"
 | |
| #include "../Prefs.h"
 | |
| #include "../ShuttleGui.h"
 | |
| #include "../commands/CommandManager.h"
 | |
| 
 | |
| #include "PrefsPanel.h"
 | |
| 
 | |
| #include "../widgets/HelpSystem.h"
 | |
| 
 | |
| #if wxUSE_ACCESSIBILITY
 | |
| #include "../widgets/WindowAccessible.h"
 | |
| #endif
 | |
| 
 | |
| 
 | |
| #if wxUSE_ACCESSIBILITY
 | |
| 
 | |
| #ifndef __WXMAC__
 | |
| 
 | |
| // Just an alias
 | |
| using TreeCtrlAx = WindowAccessible;
 | |
| 
 | |
| #else
 | |
| 
 | |
| // utility functions
 | |
| namespace {
 | |
|    template< typename Result, typename Fn >
 | |
|    Result VisitItems( const wxTreeCtrl &ctrl, Fn fn )
 | |
|    {
 | |
|       // Do preorder visit of items in the tree until satisfying a test
 | |
|       std::vector< wxTreeItemId > stack;
 | |
|       stack.push_back( ctrl.GetRootItem() );
 | |
|       unsigned position = 0;
 | |
|       while ( !stack.empty() ) {
 | |
|          auto itemId = stack.back();
 | |
|          auto pair = fn( itemId, position );
 | |
|          if ( pair.first )
 | |
|             return pair.second;
 | |
| 
 | |
|          wxTreeItemIdValue cookie;
 | |
|          auto childId = ctrl.GetFirstChild( itemId, cookie );
 | |
|          if ( childId )
 | |
|             stack.push_back( childId );
 | |
|          else do {
 | |
|             auto &id = stack.back();
 | |
|             if ( !!( id = ctrl.GetNextSibling( id ) ) )
 | |
|                break;
 | |
|          } while ( stack.pop_back(), !stack.empty() );
 | |
|          
 | |
|          ++position;
 | |
|       }
 | |
|       return {};
 | |
|    }
 | |
|    
 | |
|    unsigned FindItemPosition( const wxTreeCtrl &ctrl, wxTreeItemId id )
 | |
|    {
 | |
|       // Return the 1-based count of the item's position in the pre-order
 | |
|       // visit of the items in the tree (not counting the root item which we
 | |
|       // assume is a dummy that never matches id)
 | |
|       return VisitItems<unsigned>( ctrl,
 | |
|          [=]( wxTreeItemId itemId, unsigned position ){
 | |
|             return std::make_pair( itemId == id, position ); } );
 | |
|    }
 | |
|    
 | |
|    wxTreeItemId FindItem( const wxTreeCtrl &ctrl, int nn )
 | |
|    {
 | |
|       // The inverse of the function above
 | |
|       return VisitItems<wxTreeItemId>( ctrl,
 | |
|          [=]( wxTreeItemId itemId, unsigned position ){
 | |
|             return std::make_pair( nn == position, itemId ); } );
 | |
|    }
 | |
| }
 | |
| 
 | |
| // Define a custom class
 | |
| class TreeCtrlAx final
 | |
|    : public WindowAccessible
 | |
| {
 | |
| public:
 | |
|    TreeCtrlAx(wxTreeCtrl * ctrl);
 | |
|    virtual ~ TreeCtrlAx();
 | |
| 
 | |
|    wxAccStatus GetChild(int childId, wxAccessible** child) override;
 | |
| 
 | |
|    wxAccStatus GetChildCount(int* childCount) override;
 | |
| 
 | |
|    wxAccStatus GetDefaultAction(int childId, wxString *actionName) override;
 | |
| 
 | |
|    // Returns the description for this object or a child.
 | |
|    wxAccStatus GetDescription(int childId, wxString *description) override;
 | |
| 
 | |
|    // Gets the window with the keyboard focus.
 | |
|    // If childId is 0 and child is NULL, no object in
 | |
|    // this subhierarchy has the focus.
 | |
|    // If this object has the focus, child should be 'this'.
 | |
|    wxAccStatus GetFocus(int *childId, wxAccessible **child) override;
 | |
| 
 | |
|    // Returns help text for this object or a child, similar to tooltip text.
 | |
|    wxAccStatus GetHelpText(int childId, wxString *helpText) override;
 | |
| 
 | |
|    // Returns the keyboard shortcut for this object or child.
 | |
|    // Return e.g. ALT+K
 | |
|    wxAccStatus GetKeyboardShortcut(int childId, wxString *shortcut) override;
 | |
| 
 | |
|    // Returns the rectangle for this object (id = 0) or a child element (id > 0).
 | |
|    // rect is in screen coordinates.
 | |
|    wxAccStatus GetLocation(wxRect& rect, int elementId) override;
 | |
| 
 | |
|    // Gets the name of the specified object.
 | |
|    wxAccStatus GetName(int childId, wxString *name) override;
 | |
| 
 | |
|    // Returns a role constant.
 | |
|    wxAccStatus GetRole(int childId, wxAccRole *role) override;
 | |
| 
 | |
|    // Gets a variant representing the selected children
 | |
|    // of this object.
 | |
|    // Acceptable values:
 | |
|    // - a null variant (IsNull() returns TRUE)
 | |
|    // - a list variant (GetType() == wxT("list"))
 | |
|    // - an integer representing the selected child element,
 | |
|    //   or 0 if this object is selected (GetType() == wxT("long"))
 | |
|    // - a "void*" pointer to a wxAccessible child object
 | |
|    //wxAccStatus GetSelections(wxVariant *selections) override;
 | |
|    // leave unimplemented
 | |
| 
 | |
|    // Returns a state constant.
 | |
|    wxAccStatus GetState(int childId, long* state) override;
 | |
| 
 | |
|    // Returns a localized string representing the value for the object
 | |
|    // or child.
 | |
|    wxAccStatus GetValue(int childId, wxString* strValue) override;
 | |
| 
 | |
|    // Navigates from fromId to toId/toObject
 | |
|    // wxAccStatus Navigate(wxNavDir navDir, int fromId, int* toId, wxAccessible** toObject) override;
 | |
| 
 | |
|    // Modify focus or selection
 | |
|    wxAccStatus Select(int childId, wxAccSelectionFlags selectFlags) override;
 | |
| 
 | |
| private:
 | |
|    wxTreeCtrl *GetCtrl() { return static_cast<wxTreeCtrl*>( GetWindow() ); }
 | |
| };
 | |
| 
 | |
| TreeCtrlAx::TreeCtrlAx( wxTreeCtrl *ctrl )
 | |
| : WindowAccessible{ ctrl }
 | |
| {
 | |
| }
 | |
| 
 | |
| TreeCtrlAx::~TreeCtrlAx() = default;
 | |
| 
 | |
| wxAccStatus TreeCtrlAx::GetChild( int childId, wxAccessible** child )
 | |
| {
 | |
|    if( childId == wxACC_SELF )
 | |
|    {
 | |
|       *child = this;
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       *child = NULL;
 | |
|    }
 | |
| 
 | |
|    return wxACC_OK;
 | |
| }
 | |
| 
 | |
| wxAccStatus TreeCtrlAx::GetChildCount(int* childCount)
 | |
| {
 | |
|    auto ctrl = GetCtrl();
 | |
|    if (!ctrl)
 | |
|       return wxACC_FAIL;
 | |
| 
 | |
|    *childCount = ctrl->GetCount();
 | |
|    return wxACC_OK;
 | |
| }
 | |
| 
 | |
| wxAccStatus TreeCtrlAx::GetDefaultAction(int WXUNUSED(childId), wxString* actionName)
 | |
| {
 | |
|    actionName->clear();
 | |
| 
 | |
|    return wxACC_OK;
 | |
| }
 | |
| 
 | |
| // Returns the description for this object or a child.
 | |
| wxAccStatus TreeCtrlAx::GetDescription( int WXUNUSED(childId), wxString *description )
 | |
| {
 | |
|    description->clear();
 | |
| 
 | |
|    return wxACC_OK;
 | |
| }
 | |
| 
 | |
| // This isn't really used yet by wxWidgets as patched by Audacity for
 | |
| // Mac accessibility, as of Audacity 2.3.2, but here it is anyway, keeping the
 | |
| // analogy with TrackPanelAx
 | |
| wxAccStatus TreeCtrlAx::GetFocus( int *childId, wxAccessible **child )
 | |
| {
 | |
|    auto ctrl = GetCtrl();
 | |
|    if (!ctrl)
 | |
|       return wxACC_FAIL;
 | |
| 
 | |
|    auto item = ctrl->GetFocusedItem();
 | |
|    auto id = FindItemPosition( *ctrl, item );
 | |
|    *childId = id;
 | |
|    *child = nullptr;
 | |
|    return wxACC_OK;
 | |
| }
 | |
| 
 | |
| // Returns help text for this object or a child, similar to tooltip text.
 | |
| wxAccStatus TreeCtrlAx::GetHelpText( int WXUNUSED(childId), wxString *helpText )
 | |
| {
 | |
|    helpText->clear();
 | |
| 
 | |
|    return wxACC_OK;
 | |
| }
 | |
| 
 | |
| // Returns the keyboard shortcut for this object or child.
 | |
| // Return e.g. ALT+K
 | |
| wxAccStatus TreeCtrlAx::GetKeyboardShortcut( int WXUNUSED(childId), wxString *shortcut )
 | |
| {
 | |
|    shortcut->clear();
 | |
| 
 | |
|    return wxACC_OK;
 | |
| }
 | |
| 
 | |
| wxAccStatus TreeCtrlAx::GetLocation( wxRect& rect, int elementId )
 | |
| {
 | |
|    auto ctrl = GetCtrl();
 | |
|    if (!ctrl)
 | |
|       return wxACC_FAIL;
 | |
| 
 | |
|    if (elementId == wxACC_SELF)
 | |
|       rect = ctrl->GetRect();
 | |
|    else {
 | |
|       auto item = FindItem( *ctrl, elementId );
 | |
|       if ( !( item && ctrl->GetBoundingRect( item, rect ) ) )
 | |
|          return wxACC_INVALID_ARG;
 | |
|    }
 | |
|    rect.SetPosition( ctrl->GetParent()->ClientToScreen( rect.GetPosition() ) );
 | |
|    return wxACC_OK;
 | |
| }
 | |
| 
 | |
| wxAccStatus TreeCtrlAx::GetName(int childId, wxString* name)
 | |
| {
 | |
|    if ( childId == wxACC_SELF )
 | |
|       return WindowAccessible::GetName( childId, name );
 | |
|    else {
 | |
|       auto ctrl = GetCtrl();
 | |
|       if (!ctrl)
 | |
|          return wxACC_FAIL;
 | |
| 
 | |
|       auto item = FindItem( *ctrl, childId );
 | |
|       if ( item ) {
 | |
|          *name = ctrl->GetItemText( item );
 | |
|          return wxACC_OK;
 | |
|       }
 | |
|       else
 | |
|          return wxACC_INVALID_ARG;
 | |
|    }
 | |
| }
 | |
| 
 | |
| wxAccStatus TreeCtrlAx::GetRole( int childId, wxAccRole* role )
 | |
| {
 | |
|    // Not sure if this correct, but it is analogous with what we use in
 | |
|    // TrackPanel
 | |
|    *role =
 | |
|       childId == wxACC_SELF ? wxROLE_SYSTEM_PANE : wxROLE_SYSTEM_STATICTEXT;
 | |
|    return wxACC_OK;
 | |
| }
 | |
| 
 | |
| // Returns a state constant.
 | |
| wxAccStatus TreeCtrlAx::GetState(int childId, long* state)
 | |
| {
 | |
|    auto ctrl = GetCtrl();
 | |
|    if (!ctrl)
 | |
|       return wxACC_FAIL;
 | |
| 
 | |
|    *state = wxACC_STATE_SYSTEM_FOCUSABLE | wxACC_STATE_SYSTEM_SELECTABLE;
 | |
| 
 | |
|    if ( childId != wxACC_SELF ) {
 | |
|       auto item = FindItem( *ctrl, childId );
 | |
|       if (item) {
 | |
|          if( item == ctrl->GetFocusedItem() )
 | |
|             *state |= wxACC_STATE_SYSTEM_FOCUSED;
 | |
| 
 | |
|          if( item == ctrl->GetSelection() )
 | |
|             *state |= wxACC_STATE_SYSTEM_SELECTED;
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    return wxACC_OK;
 | |
| }
 | |
| 
 | |
| // Returns a localized string representing the value for the object
 | |
| // or child.
 | |
| wxAccStatus TreeCtrlAx::GetValue(int childId, wxString* strValue)
 | |
| {
 | |
|    *strValue = wxString{};
 | |
|    return wxACC_OK;
 | |
| }
 | |
| 
 | |
| //wxAccStatus TreeCtrlAx::Navigate(
 | |
| //   wxNavDir navDir, int fromId, int* toId, wxAccessible** toObject)
 | |
| //{
 | |
| //   to do
 | |
| //}
 | |
| 
 | |
| // Modify focus or selection
 | |
| wxAccStatus TreeCtrlAx::Select(int childId, wxAccSelectionFlags selectFlags)
 | |
| {
 | |
|    auto ctrl = GetCtrl();
 | |
|    if (!ctrl)
 | |
|       return wxACC_FAIL;
 | |
| 
 | |
|    if (childId != wxACC_SELF) {
 | |
|       int childCount;
 | |
|       GetChildCount( &childCount );
 | |
|       if (childId > childCount)
 | |
|            return wxACC_FAIL;
 | |
| 
 | |
|       auto item = FindItem( *ctrl, childId );
 | |
|       if ( item ) {
 | |
|          if (selectFlags == wxACC_SEL_TAKEFOCUS)
 | |
|             ctrl->SetFocusedItem( item );
 | |
|          else if (selectFlags == wxACC_SEL_TAKESELECTION)
 | |
|             ctrl->SelectItem( item );
 | |
|          else
 | |
|             return wxACC_NOT_IMPLEMENTED;
 | |
|          return wxACC_OK;
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    return wxACC_NOT_IMPLEMENTED;
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| #endif
 | |
| 
 | |
| 
 | |
| // PrefsPanel might move out into its own file in due ocurse.
 | |
| PluginPath PrefsPanel::GetPath(){      return BUILTIN_PREFS_PANEL_PREFIX + GetSymbol().Internal(); }
 | |
| VendorSymbol PrefsPanel::GetVendor(){  return XO("Audacity");}
 | |
| wxString PrefsPanel::GetVersion(){     return AUDACITY_VERSION_STRING;}
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| BEGIN_EVENT_TABLE(PrefsDialog, wxDialogWrapper)
 | |
|    EVT_BUTTON(wxID_OK, PrefsDialog::OnOK)
 | |
|    EVT_BUTTON(wxID_CANCEL, PrefsDialog::OnCancel)
 | |
|    EVT_BUTTON(wxID_PREVIEW, PrefsDialog::OnPreview)
 | |
|    EVT_BUTTON(wxID_HELP, PrefsDialog::OnHelp)
 | |
|    EVT_TREE_KEY_DOWN(wxID_ANY, PrefsDialog::OnTreeKeyDown) // Handles key events when tree has focus
 | |
| END_EVENT_TABLE()
 | |
| 
 | |
| 
 | |
| class wxTreebookExt final : public wxTreebook
 | |
| {
 | |
| public:
 | |
|    wxTreebookExt( wxWindow *parent,
 | |
|       wxWindowID id, const TranslatableString &titlePrefix)
 | |
|       : wxTreebook( parent, id )
 | |
|       , mTitlePrefix(titlePrefix)
 | |
|    {;};
 | |
|    ~wxTreebookExt(){;};
 | |
|    int ChangeSelection(size_t n) override;
 | |
|    int SetSelection(size_t n) override;
 | |
|    const TranslatableString mTitlePrefix;
 | |
| };
 | |
| 
 | |
| 
 | |
| int wxTreebookExt::ChangeSelection(size_t n) {
 | |
|    int i = wxTreebook::ChangeSelection(n);
 | |
|    wxString Temp = GetPageText( n );
 | |
|    static_cast<wxDialog*>(GetParent())->SetTitle( Temp );
 | |
|    static_cast<wxDialog*>(GetParent())->SetName( Temp );
 | |
|    return i;
 | |
| };
 | |
| 
 | |
| int wxTreebookExt::SetSelection(size_t n)
 | |
| {
 | |
|    int i = wxTreebook::SetSelection(n);
 | |
|    auto Temp = mTitlePrefix.Translation() + wxT(" ") + GetPageText( n );
 | |
|    static_cast<wxDialog*>(GetParent())->SetTitle( Temp );
 | |
|    static_cast<wxDialog*>(GetParent())->SetName( Temp );
 | |
| 
 | |
|    PrefsPanel *const panel = static_cast<PrefsPanel *>(GetPage(n));
 | |
|    const bool showHelp = (!panel->HelpPageName().empty());
 | |
|    const bool showPreview = panel->ShowsPreviewButton();
 | |
|    wxWindow *const helpButton = wxWindow::FindWindowById(wxID_HELP, GetParent());
 | |
|    wxWindow *const previewButton = wxWindow::FindWindowById(wxID_PREVIEW, GetParent());
 | |
| 
 | |
|    if (helpButton) {
 | |
|       if (showHelp) {
 | |
|          wxAcceleratorEntry entries[1];
 | |
| #if defined(__WXMAC__)
 | |
|          // Is there a standard shortcut on Mac?
 | |
| #else
 | |
|          entries[0].Set(wxACCEL_NORMAL, (int) WXK_F1, wxID_HELP);
 | |
| #endif
 | |
|          wxAcceleratorTable accel(1, entries);
 | |
|          this->SetAcceleratorTable(accel);
 | |
|       }
 | |
|       else {
 | |
|          this->SetAcceleratorTable(wxNullAcceleratorTable);
 | |
|       }
 | |
| 
 | |
|       const bool changed = helpButton->Show(showHelp);
 | |
|       if (changed)
 | |
|          GetParent()->Layout();
 | |
|    }
 | |
| 
 | |
|    if (previewButton) { // might still be NULL during population
 | |
|       const bool changed = previewButton->Show(showPreview);
 | |
|       if (changed)
 | |
|          GetParent()->Layout();
 | |
|    }
 | |
| 
 | |
|    return i;
 | |
| }
 | |
| 
 | |
| namespace {
 | |
|    struct Entry{
 | |
|       unsigned sequenceNumber;
 | |
|       PrefsDialog::PrefsNode node;
 | |
|       
 | |
|       bool operator < ( const Entry &other ) const
 | |
|       { return sequenceNumber < other.sequenceNumber; }
 | |
|    };
 | |
|    using Entries = std::vector< Entry >;
 | |
|    namespace Prefs {
 | |
|       Entries &Registry()
 | |
|       {
 | |
|          static Entries result;
 | |
|          return result;
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| PrefsPanel::Registration::Registration( unsigned sequenceNumber,
 | |
|    const Factory &factory,
 | |
|    unsigned nChildren,
 | |
|    bool expanded )
 | |
| {
 | |
|    auto ®istry = Prefs::Registry();
 | |
|    Entry entry{ sequenceNumber, { factory, nChildren, expanded } };
 | |
|    const auto end = registry.end();
 | |
|    // Find insertion point:
 | |
|    auto iter = std::lower_bound( registry.begin(), end, entry );
 | |
|    // There should not be duplicate sequence numbers:
 | |
|    wxASSERT( iter == end || entry < *iter );
 | |
|    registry.insert( iter, entry );
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| struct PrefsItem final : Registry::ConcreteGroupItem<false> {
 | |
|    PrefsPanel::Factory factory;
 | |
|    bool expanded{ false };
 | |
| 
 | |
|    PrefsItem( const wxString &name,
 | |
|       const PrefsPanel::Factory &factory_, bool expanded_ )
 | |
|          : ConcreteGroupItem<false>{ name }
 | |
|          , factory{ factory_ }, expanded{ expanded_ }
 | |
|    {}
 | |
| };
 | |
| 
 | |
| // Collects registry tree nodes into a vector, in preorder.
 | |
| struct PrefsItemVisitor final : Registry::Visitor {
 | |
|    PrefsItemVisitor( PrefsDialog::Factories &factories_ )
 | |
|       : factories{ factories_ }
 | |
|    {
 | |
|       childCounts.push_back( 0 );
 | |
|    }
 | |
|    void BeginGroup( Registry::GroupItem &item, const Path & ) override
 | |
|    {
 | |
|       auto pItem = dynamic_cast<PrefsItem*>( &item );
 | |
|       if (!pItem)
 | |
|          return;
 | |
|       indices.push_back( factories.size() );
 | |
|       factories.emplace_back( pItem->factory, 0, pItem->expanded );
 | |
|       ++childCounts.back();
 | |
|       childCounts.push_back( 0 );
 | |
|    }
 | |
|    void EndGroup( Registry::GroupItem &item, const Path & ) override
 | |
|    {
 | |
|       auto pItem = dynamic_cast<PrefsItem*>( &item );
 | |
|       if (!pItem)
 | |
|          return;
 | |
|       auto &factory = factories[ indices.back() ];
 | |
|       factory.nChildren = childCounts.back();
 | |
|       childCounts.pop_back();
 | |
|       indices.pop_back();
 | |
|    }
 | |
| 
 | |
|    PrefsDialog::Factories &factories;
 | |
|    std::vector<size_t> childCounts;
 | |
|    std::vector<size_t> indices;
 | |
| };
 | |
| 
 | |
| 
 | |
| const auto PathStart = wxT("Preferences");
 | |
|    
 | |
| }
 | |
| 
 | |
| 
 | |
| PrefsDialog::Factories
 | |
| &PrefsDialog::DefaultFactories()
 | |
| {
 | |
|    // Once only, cause initial population of preferences for the ordering
 | |
|    // of some preference pages that used to be given in a table but are now
 | |
|    // separately registered in several .cpp files; the sequence of registration
 | |
|    // depends on unspecified accidents of static initialization order across
 | |
|    // compilation units, so we need something specific here to preserve old
 | |
|    // default appearance of the preference dialog.
 | |
|    // But this needs only to mention some strings -- there is no compilation or
 | |
|    // link dependency of this source file on those other implementation files.
 | |
|    static Registry::OrderingPreferenceInitializer init{
 | |
|       PathStart,
 | |
|       {
 | |
|          {wxT(""),
 | |
|    wxT("Device,Playback,Recording,Quality,GUI,Tracks,ImportExport,Projects,Directories,Warnings,Effects,KeyConfig,Mouse")
 | |
|          },
 | |
|          {wxT("/Tracks"), wxT("TracksBehaviors,Spectrum")},
 | |
|       }
 | |
|    };
 | |
|    static Factories factories;
 | |
|    static std::once_flag flag;
 | |
| 
 | |
|    std::call_once( flag, []{
 | |
|       for ( const auto &entry : Prefs::Registry() ) {
 | |
|          factories.push_back( entry.node );
 | |
|       }
 | |
|    } );
 | |
|    return factories;
 | |
| }
 | |
| 
 | |
| 
 | |
| PrefsDialog::PrefsDialog(
 | |
|    wxWindow * parent, AudacityProject *pProject,
 | |
|    const TranslatableString &titlePrefix, Factories &factories)
 | |
| :  wxDialogWrapper(parent, wxID_ANY, XO("Audacity Preferences"),
 | |
|             wxDefaultPosition,
 | |
|             wxDefaultSize,
 | |
|             wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
 | |
| , mFactories(factories)
 | |
| , mTitlePrefix(titlePrefix)
 | |
| {
 | |
|    wxASSERT(factories.size() > 0);
 | |
|    const bool uniquePage = (factories.size() == 1);
 | |
|    SetLayoutDirection(wxLayout_LeftToRight);
 | |
| 
 | |
|    ShuttleGui S(this, eIsCreating);
 | |
| 
 | |
|    S.StartVerticalLay(true);
 | |
|    {
 | |
|       wxASSERT(factories.size() > 0);
 | |
|       if (!uniquePage) {
 | |
|          mCategories = safenew wxTreebookExt(S.GetParent(), wxID_ANY, mTitlePrefix);
 | |
| #if wxUSE_ACCESSIBILITY
 | |
|          // so that name can be set on a standard control
 | |
|          mCategories->GetTreeCtrl()->SetAccessible(
 | |
|             safenew TreeCtrlAx(mCategories->GetTreeCtrl()));
 | |
| #endif
 | |
|          // RJH: Prevent NVDA from reading "treeCtrl"
 | |
|          mCategories->GetTreeCtrl()->SetName(_("Category"));
 | |
|          S.StartHorizontalLay(wxALIGN_LEFT | wxEXPAND, true);
 | |
|          {
 | |
|             S.Prop(1)
 | |
|                .Position(wxEXPAND)
 | |
|                .AddWindow(mCategories);
 | |
| 
 | |
|             {
 | |
|                typedef std::pair<int, int> IntPair;
 | |
|                std::vector<IntPair> stack;
 | |
|                int iPage = 0;
 | |
|                for (Factories::const_iterator it = factories.begin(), end = factories.end();
 | |
|                   it != end; ++it, ++iPage)
 | |
|                {
 | |
|                   const PrefsNode &node = *it;
 | |
|                   const PrefsPanel::Factory &factory = node.factory;
 | |
|                   wxWindow *const w = factory(mCategories, wxID_ANY, pProject);
 | |
|                   if (stack.empty())
 | |
|                      // Parameters are: AddPage(page, name, IsSelected, imageId).
 | |
|                      mCategories->AddPage(w, w->GetName(), false, 0);
 | |
|                   else {
 | |
|                      IntPair &top = *stack.rbegin();
 | |
|                      mCategories->InsertSubPage(top.first, w, w->GetName(), false, 0);
 | |
|                      if (--top.second == 0) {
 | |
|                         // Expand all nodes before the layout calculation
 | |
|                         mCategories->ExpandNode(top.first, true);
 | |
|                         stack.pop_back();
 | |
|                      }
 | |
|                   }
 | |
|                   if (node.nChildren > 0)
 | |
|                      stack.push_back(IntPair(iPage, node.nChildren));
 | |
|                }
 | |
|             }
 | |
|          }
 | |
|          S.EndHorizontalLay();
 | |
|       }
 | |
|       else {
 | |
|          // TODO: Look into getting rid of mUniquePage and instead
 | |
|          // adding into mCategories, so there is just one page in mCategories.
 | |
|          // And then hiding the treebook.
 | |
| 
 | |
|          // Unique page, don't show the factory
 | |
|          const PrefsNode &node = factories[0];
 | |
|          const PrefsPanel::Factory &factory = node.factory;
 | |
|          mUniquePage = factory(S.GetParent(), wxID_ANY, pProject);
 | |
|          wxWindow * uniquePageWindow = S.Prop(1)
 | |
|             .Position(wxEXPAND)
 | |
|             .AddWindow(mUniquePage);
 | |
|          // We're not in the wxTreebook, so add the accelerator here
 | |
|          wxAcceleratorEntry entries[1];
 | |
| #if defined(__WXMAC__)
 | |
|          // Is there a standard shortcut on Mac?
 | |
| #else
 | |
|          entries[0].Set(wxACCEL_NORMAL, (int) WXK_F1, wxID_HELP);
 | |
| #endif
 | |
|          wxAcceleratorTable accel(1, entries);
 | |
|          uniquePageWindow->SetAcceleratorTable(accel);
 | |
|       }
 | |
|    }
 | |
|    S.EndVerticalLay();
 | |
| 
 | |
|    S.AddStandardButtons(eOkButton | eCancelButton | ePreviewButton | eHelpButton);
 | |
| 
 | |
|    if (mUniquePage && !mUniquePage->ShowsPreviewButton()) {
 | |
|       wxWindow *const previewButton =
 | |
|          wxWindow::FindWindowById(wxID_PREVIEW, GetParent());
 | |
|       previewButton->Show(false);
 | |
|    }
 | |
| 
 | |
| #if defined(__WXGTK__)
 | |
|    if (mCategories)
 | |
|       mCategories->GetTreeCtrl()->EnsureVisible(mCategories->GetTreeCtrl()->GetRootItem());
 | |
| #endif
 | |
| 
 | |
| //   mCategories->SetMaxSize({ 790, 600 });  // 790 = 800 - (border * 2)
 | |
|    Layout();
 | |
|    Fit();
 | |
|    wxSize sz = GetSize();
 | |
| 
 | |
|    // Collapse nodes only after layout so the tree is wide enough
 | |
|    if (mCategories)
 | |
|    {
 | |
|       int iPage = 0;
 | |
|       for (Factories::const_iterator it = factories.begin(), end = factories.end();
 | |
|          it != end; ++it, ++iPage)
 | |
|          mCategories->ExpandNode(iPage, it->expanded);
 | |
|    }
 | |
| 
 | |
|    // This ASSERT was originally used to limit us to 800 x 600.
 | |
|    // However, the range of screen sizes and dpi of modern (2018) displays
 | |
|    // makes pixel dimensions an inadequate measure of usability, so
 | |
|    // now we only ASSERT that preferences will fit in the client display
 | |
|    // rectangle of the developer / tester's monitor.
 | |
|    // Use scrollers when necessary to ensure that preference pages will
 | |
|    // be fully visible.
 | |
|    wxRect screenRect(wxGetClientDisplayRect());
 | |
|    wxASSERT_MSG(sz.x <= screenRect.width && sz.y <= screenRect.height, wxT("Preferences dialog exceeds max size"));
 | |
| 
 | |
|    sz.DecTo(screenRect.GetSize());
 | |
| 
 | |
|    if( !mUniquePage ){
 | |
|       int prefWidth, prefHeight;
 | |
|       gPrefs->Read(wxT("/Prefs/Width"), &prefWidth, sz.x);
 | |
|       gPrefs->Read(wxT("/Prefs/Height"), &prefHeight, wxMax(480,sz.y));
 | |
| 
 | |
|       wxSize prefSize = wxSize(prefWidth, prefHeight);
 | |
|       prefSize.DecTo(screenRect.GetSize());
 | |
|       SetSize(prefSize);
 | |
|       InvalidateBestSize();
 | |
|       Layout();
 | |
|    }
 | |
|    SetMinSize(sz);
 | |
| 
 | |
|    // Center after all that resizing, but make sure it doesn't end up
 | |
|    // off-screen
 | |
|    CentreOnParent();
 | |
| }
 | |
| 
 | |
| PrefsDialog::~PrefsDialog()
 | |
| {
 | |
| }
 | |
| 
 | |
| int PrefsDialog::ShowModal()
 | |
| {
 | |
|    if (mCategories) {
 | |
|       /* long is signed, size_t is unsigned. On some platforms they are different
 | |
|        * lengths as well. So we must check that the stored category is both > 0
 | |
|        * and within the possible range of categories, making the first check on the
 | |
|        * _signed_ value to avoid issues when converting an unsigned one.
 | |
|        */
 | |
|       long selected = GetPreferredPage();
 | |
|       if (selected < 0 || size_t(selected) >= mCategories->GetPageCount())
 | |
|          selected = 0;  // clamp to available range of tabs
 | |
|       mCategories->SetSelection(selected);
 | |
|    }
 | |
|    else {
 | |
|       auto Temp = mTitlePrefix;
 | |
|       Temp.Join( Verbatim( mUniquePage->GetLabel() ), wxT(" ") );
 | |
|       SetTitle(Temp);
 | |
|       SetName(Temp);
 | |
|    }
 | |
| 
 | |
|    return wxDialogWrapper::ShowModal();
 | |
| }
 | |
| 
 | |
| void PrefsDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
 | |
| {
 | |
|    RecordExpansionState();
 | |
| 
 | |
|    if (mCategories) {
 | |
|       for (size_t i = 0; i < mCategories->GetPageCount(); i++) {
 | |
|          ((PrefsPanel *)mCategories->GetPage(i))->Cancel();
 | |
|       }
 | |
|    }
 | |
|    else
 | |
|       mUniquePage->Cancel();
 | |
| 
 | |
|    // Remember modified dialog size, even if cancelling.
 | |
|    if( !mUniquePage ){
 | |
|       wxSize sz = GetSize();
 | |
|       gPrefs->Write(wxT("/Prefs/Width"), sz.x);
 | |
|       gPrefs->Write(wxT("/Prefs/Height"), sz.y);
 | |
|    }
 | |
|    gPrefs->Flush();
 | |
| 
 | |
|    EndModal(false);
 | |
| }
 | |
| 
 | |
| PrefsPanel * PrefsDialog::GetCurrentPanel()
 | |
| {
 | |
|    if( mCategories) 
 | |
|       return static_cast<PrefsPanel*>(mCategories->GetCurrentPage());
 | |
|    else
 | |
|    {
 | |
|       wxASSERT( mUniquePage );
 | |
|       return mUniquePage;
 | |
|    }
 | |
| }
 | |
| 
 | |
| void PrefsDialog::OnPreview(wxCommandEvent & WXUNUSED(event))
 | |
| {
 | |
|    GetCurrentPanel()->Preview();
 | |
| }
 | |
| 
 | |
| void PrefsDialog::OnHelp(wxCommandEvent & WXUNUSED(event))
 | |
| {
 | |
|    wxString page = GetCurrentPanel()->HelpPageName();
 | |
|    HelpSystem::ShowHelp(this, page, true);
 | |
| }
 | |
| 
 | |
| void PrefsDialog::ShuttleAll( ShuttleGui & S)
 | |
| {
 | |
|    // Validate all pages first
 | |
|    if (mCategories) {
 | |
|       for (size_t i = 0; i < mCategories->GetPageCount(); i++) {
 | |
|          S.ResetId();
 | |
|          PrefsPanel *panel = (PrefsPanel *)mCategories->GetPage(i);
 | |
|          panel->PopulateOrExchange( S );
 | |
|       }
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       S.ResetId();
 | |
|       mUniquePage->PopulateOrExchange( S );
 | |
|    }
 | |
| }
 | |
| 
 | |
| void PrefsDialog::OnTreeKeyDown(wxTreeEvent & event)
 | |
| {
 | |
|    if(event.GetKeyCode() == WXK_RETURN)
 | |
|       OnOK(event);
 | |
|    else
 | |
|       event.Skip(); // Ensure standard behavior when enter is not pressed
 | |
| }
 | |
| 
 | |
| void PrefsDialog::OnOK(wxCommandEvent & WXUNUSED(event))
 | |
| {
 | |
|    RecordExpansionState();
 | |
| 
 | |
|    // Validate all pages first
 | |
|    if (mCategories) {
 | |
|       for (size_t i = 0; i < mCategories->GetPageCount(); i++) {
 | |
|          PrefsPanel *panel = (PrefsPanel *)mCategories->GetPage(i);
 | |
| 
 | |
|          // The dialog doesn't end until all the input is valid
 | |
|          if (!panel->Validate()) {
 | |
|             mCategories->SetSelection(i);
 | |
|             return;
 | |
|          }
 | |
|       }
 | |
|    }
 | |
|    else {
 | |
|       if (!mUniquePage->Validate())
 | |
|          return;
 | |
|    }
 | |
| 
 | |
|    // flush now so toolbars will know their position.
 | |
|    gPrefs->Flush();
 | |
|    if (mCategories) {
 | |
|       // Now apply the changes
 | |
|       // Reverse order - so Track Name is updated before language change
 | |
|       // A workaround for Bug 1661
 | |
|       for (int i = (int)mCategories->GetPageCount()-1; i>= 0; i--) {
 | |
|          PrefsPanel *panel = (PrefsPanel *)mCategories->GetPage(i);
 | |
| 
 | |
|          panel->Preview();
 | |
|          panel->Commit();
 | |
|       }
 | |
|    }
 | |
|    else {
 | |
|       mUniquePage->Preview();
 | |
|       mUniquePage->Commit();
 | |
|    }
 | |
| 
 | |
|    if( !mUniquePage ){
 | |
|       wxSize sz = GetSize();
 | |
|       gPrefs->Write(wxT("/Prefs/Width"), sz.x);
 | |
|       gPrefs->Write(wxT("/Prefs/Height"), sz.y);
 | |
|    }
 | |
|    gPrefs->Flush();
 | |
| 
 | |
|    SavePreferredPage();
 | |
| 
 | |
| #if USE_PORTMIXER
 | |
|    auto gAudioIO = AudioIOBase::Get();
 | |
|    if (gAudioIO) {
 | |
|       // We cannot have opened this dialog if gAudioIO->IsAudioTokenActive(),
 | |
|       // per the setting of AudioIONotBusyFlag and AudioIOBusyFlag in
 | |
|       // AudacityProject::GetUpdateFlags().
 | |
|       // However, we can have an invalid audio token (so IsAudioTokenActive()
 | |
|       // is false), but be monitoring.
 | |
|       // If monitoring, have to stop the stream, so HandleDeviceChange() can work.
 | |
|       // We could disable the Preferences command while monitoring, i.e.,
 | |
|       // set AudioIONotBusyFlag/AudioIOBusyFlag according to monitoring, as well.
 | |
|       // Instead allow it because unlike recording, for example, monitoring
 | |
|       // is not clearly something that should prohibit opening prefs.
 | |
|       // TODO: We *could* be smarter in this method and call HandleDeviceChange()
 | |
|       // only when the device choices actually changed. True of lots of prefs!
 | |
|       // As is, we always stop monitoring before handling the device change.
 | |
|       if (gAudioIO->IsMonitoring())
 | |
|       {
 | |
|          gAudioIO->StopStream();
 | |
|          while (gAudioIO->IsBusy())
 | |
|             wxMilliSleep(100);
 | |
|       }
 | |
|       gAudioIO->HandleDeviceChange();
 | |
|    }
 | |
| #endif
 | |
| 
 | |
|    // PRL:  Is the following concern still valid, now that prefs update is
 | |
|    //      handled instead by delayed event processing?
 | |
| 
 | |
|    // LL:  wxMac can't handle recreating the menus when this dialog is still active,
 | |
|    //      so AudacityProject::UpdatePrefs() or any of the routines it calls must
 | |
|    //      not cause MenuCreator::RebuildMenuBar() to be executed.
 | |
| 
 | |
|    wxTheApp->AddPendingEvent(wxCommandEvent{ EVT_PREFS_UPDATE });
 | |
| 
 | |
|    if( IsModal() )
 | |
|       EndModal(true);
 | |
|    else
 | |
|       Destroy();
 | |
| }
 | |
| 
 | |
| void PrefsDialog::SelectPageByName(const wxString &pageName)
 | |
| {
 | |
|    if (mCategories) {
 | |
|       size_t n = mCategories->GetPageCount();
 | |
| 
 | |
|       for (size_t i = 0; i < n; i++) {
 | |
|          if (mCategories->GetPageText(i) == pageName) {
 | |
|             mCategories->SetSelection(i);
 | |
|             return;
 | |
|          }
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| int PrefsDialog::GetSelectedPage() const
 | |
| {
 | |
|    if (mCategories)
 | |
|       return mCategories->GetSelection();
 | |
|    else
 | |
|       return 0;
 | |
| }
 | |
| 
 | |
| GlobalPrefsDialog::GlobalPrefsDialog(
 | |
|    wxWindow * parent, AudacityProject *pProject, Factories &factories)
 | |
|    : PrefsDialog(parent, pProject, XO("Preferences:"), factories)
 | |
| {
 | |
| }
 | |
| 
 | |
| GlobalPrefsDialog::~GlobalPrefsDialog()
 | |
| {
 | |
| }
 | |
| 
 | |
| long GlobalPrefsDialog::GetPreferredPage()
 | |
| {
 | |
|    long prefscat = gPrefs->Read(wxT("/Prefs/PrefsCategory"), 0L);
 | |
|    return prefscat;
 | |
| }
 | |
| 
 | |
| void GlobalPrefsDialog::SavePreferredPage()
 | |
| {
 | |
|    gPrefs->Write(wxT("/Prefs/PrefsCategory"), (long)GetSelectedPage());
 | |
|    gPrefs->Flush();
 | |
| }
 | |
| 
 | |
| void PrefsDialog::RecordExpansionState()
 | |
| {
 | |
|    // Remember expansion state of the tree control
 | |
|    if (mCategories)
 | |
|    {
 | |
|       int iPage = 0;
 | |
|       for (Factories::iterator it = mFactories.begin(), end = mFactories.end();
 | |
|          it != end; ++it, ++iPage)
 | |
|          it->expanded = mCategories->IsNodeExpanded(iPage);
 | |
|    }
 | |
|    else
 | |
|       mFactories[0].expanded = true;
 | |
| }
 | |
| 
 | |
| PrefsPanel::~PrefsPanel()
 | |
| {
 | |
| }
 | |
| 
 | |
| void PrefsPanel::Cancel()
 | |
| {
 | |
| }
 | |
| 
 | |
| bool PrefsPanel::ShowsPreviewButton()
 | |
| {
 | |
|    return false;
 | |
| }
 | |
| 
 | |
| wxString PrefsPanel::HelpPageName()
 | |
| {
 | |
|    return wxEmptyString;
 | |
| }
 | |
| 
 | |
| #include <wx/frame.h>
 | |
| #include "../Menus.h"
 | |
| #include "../Project.h"
 | |
| 
 | |
| void DoReloadPreferences( AudacityProject &project )
 | |
| {
 | |
|    {
 | |
|       GlobalPrefsDialog dialog(
 | |
|          &GetProjectFrame( project ) /* parent */, &project );
 | |
|       wxCommandEvent Evt;
 | |
|       //dialog.Show();
 | |
|       dialog.OnOK(Evt);
 | |
|    }
 | |
| 
 | |
|    // LL:  Moved from PrefsDialog since wxWidgets on OSX can't deal with
 | |
|    //      rebuilding the menus while the PrefsDialog is still in the modal
 | |
|    //      state.
 | |
|    for (auto p : AllProjects{}) {
 | |
|       MenuManager::Get(*p).RebuildMenuBar(*p);
 | |
| // TODO: The comment below suggests this workaround is obsolete.
 | |
| #if defined(__WXGTK__)
 | |
|       // Workaround for:
 | |
|       //
 | |
|       //   http://bugzilla.audacityteam.org/show_bug.cgi?id=458
 | |
|       //
 | |
|       // This workaround should be removed when Audacity updates to wxWidgets
 | |
|       // 3.x which has a fix.
 | |
|       auto &window = GetProjectFrame( *p );
 | |
|       wxRect r = window.GetRect();
 | |
|       window.SetSize(wxSize(1,1));
 | |
|       window.SetSize(r.GetSize());
 | |
| #endif
 | |
|    }
 | |
| }
 |