Codebase list audacity / 093b334
Merge branch 'upstream' Benjamin Drung 9 years ago
11 changed file(s) with 4279 addition(s) and 4279 deletion(s). Raw diff Collapse all Expand all
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 ModTrackPanelCallback.cpp
5
6 James Crook
7
8 Audacity is free software.
9 This file is licensed under the wxWidgets license, see License.txt
10
11 ********************************************************************//**
12
13 \class ModTrackPanelCallback
14 \brief ModTrackPanelCallback is a class containing all the callback
15 functions for the second generation TrackPanel. These functions are
16 added into the standard Audacity Project Menus.
17
18 *//*****************************************************************//**
19
20 \class ModTrackPanelCommandFunctor
21 \brief We create one of these functors for each menu item or
22 command which we register with the Command Manager. These take the
23 click from the menu into the actual function to be called.
24
25 *//********************************************************************/
26
27 #include <wx/wx.h>
28 #include "ModTrackPanelCallback.h"
29 #include "Audacity.h"
30 #include "ShuttleGui.h"
31 #include "Project.h"
32 #include "LoadModules.h"
33 #include "Registrar.h"
34 #include "TrackPanel2.h"
35
36 /*
37 There are several functions that can be used in a GUI module.
38
39 //#define versionFnName "GetVersionString"
40 If the version is wrong, the module will be rejected.
41 That is it will be loaded and then unloaded.
42
43 //#define ModuleDispatchName "ModuleDispatch"
44 The most useful function. See the example in this
45 file. It has several cases/options in it.
46
47 //#define scriptFnName "RegScriptServerFunc"
48 This function is run from a non gui thread. It was originally
49 created for the benefit of mod-script-pipe.
50
51 //#define mainPanelFnName "MainPanelFunc"
52 This function is the hijacking function, to take over Audacity
53 and replace the main project window with our own wxFrame.
54
55 */
56
57 // The machinery here is somewhat overkill for what we need.
58 // It allows us to add lots of menu and other actions into Audacity.
59 // We need to jump through these hoops even if only adding
60 // two menu items into Audacity.
61
62 // The OnFunc functrions are functions which can be invoked
63 // by Audacity. Mostly they are for menu items. They could
64 // be for buttons. They could be for commands invokable by
65 // script (but no examples of that yet).
66 class ModTrackPanelCallback
67 {
68 public:
69 void OnFuncShowAudioExplorer();
70 void OnFuncReplaceTrackPanel();
71 };
72
73 typedef void (ModTrackPanelCallback::*ModTrackPanelCommandFunction)();
74
75 // We have an instance of this CommandFunctor for each
76 // instance of a command we attach to Audacity.
77 // Although the commands have void argument,
78 // they do receive an instance of ModTrackPanelCallback as a 'this',
79 // so if we want to, we can pass data to each function instance.
80 class ModTrackPanelCommandFunctor:public CommandFunctor
81 {
82 public:
83 ModTrackPanelCommandFunctor(ModTrackPanelCallback *pData,
84 ModTrackPanelCommandFunction pFunction);
85 virtual void operator()(int index = 0, const wxEvent * evt=NULL);
86 public:
87 ModTrackPanelCallback * mpData;
88 ModTrackPanelCommandFunction mpFunction;
89 };
90
91 // If pData is NULL we will later be passing a NULL 'this' to pFunction.
92 // This may be quite OK if the class function is written as if it
93 // could be static.
94 ModTrackPanelCommandFunctor::ModTrackPanelCommandFunctor(ModTrackPanelCallback *pData,
95 ModTrackPanelCommandFunction pFunction)
96 {
97 mpData = pData;
98 mpFunction = pFunction;
99 }
100
101 // The dispatching function in the command functor.
102 void ModTrackPanelCommandFunctor::operator()(int index, const wxEvent * WXUNUSED(evt) )
103 {
104 (mpData->*(mpFunction))();
105 }
106
107 #define ModTrackPanelFN(X) new ModTrackPanelCommandFunctor(pModTrackPanelCallback, \
108 (ModTrackPanelCommandFunction)(&ModTrackPanelCallback::X))
109
110
111 void ModTrackPanelCallback::OnFuncShowAudioExplorer()
112 {
113 int k=3;
114 Registrar::ShowNewPanel();
115 }
116
117 void ModTrackPanelCallback::OnFuncReplaceTrackPanel()
118 {
119 // Upgrade the factory. Now all TrackPanels will be created as TrackPanel 2's
120
121 #if 0
122 AudacityProject *p = GetActiveProject();
123 wxASSERT( p!= NULL );
124 // Change it's type (No new storage allocated).
125 TrackPanel2::Upgrade( &p->mTrackPanel );
126 int k=4;
127 #endif
128 }
129
130 // Oooh look, we're using a NULL object, and hence a NULL 'this'.
131 // That's OK.
132 ModTrackPanelCallback * pModTrackPanelCallback=NULL;
133
134 //This is the DLL related machinery that actually gets called by Audacity
135 //as prt of loading and using a DLL.
136 //It is MUCH simpler to use C for this interface because then the
137 //function names are not 'mangled'.
138 //The function names are important, because they are what Audacity looks
139 //for. Change the name and they won't be found.
140 //Change the signature, e.g. return type, and you probably have a crash.
141 extern "C" {
142 // GetVersionString
143 // REQUIRED for the module to be accepted by Audacity.
144 // Without it Audacity will see a version number mismatch.
145 MOD_TRACK_PANEL_DLL_API wxChar * GetVersionString()
146 {
147 // Make sure that this version of the module requires the version
148 // of Audacity it is built with.
149 // For now, the versions must match exactly for Audacity to
150 // agree to load the module.
151 return AUDACITY_VERSION_STRING;
152 }
153
154 // This is the function that connects us to Audacity.
155 MOD_TRACK_PANEL_DLL_API int ModuleDispatch(ModuleDispatchTypes type)
156 {
157 switch (type)
158 {
159 case AppInitialized:
160 Registrar::Start();
161 // Demand that all track panels be created using the TrackPanel2Factory.
162 TrackPanel::FactoryFunction = TrackPanel2Factory;
163 break;
164 case AppQuiting:
165 Registrar::Finish();
166 break;
167 case ProjectInitialized:
168 case MenusRebuilt:
169 {
170 AudacityProject *p = GetActiveProject();
171 if( p== NULL )
172 return 0;
173
174 wxMenuBar * pBar = p->GetMenuBar();
175 wxMenu * pMenu = pBar->GetMenu( 7 ); // Menu 7 is the Analyze Menu.
176 CommandManager * c = p->GetCommandManager();
177
178 c->SetToMenu( pMenu );
179 c->AddSeparator();
180 // We add two new commands into the Analyze menu.
181 c->AddItem( _T("Extra Dialog..."), _T("Experimental Extra Dialog for whatever you want."),
182 ModTrackPanelFN( OnFuncShowAudioExplorer ) );
183 //Second menu tweak no longer needed as we always make TrackPanel2's.
184 //c->AddItem( _T("Replace TrackPanel..."), _T("Replace Current TrackPanel with TrackPanel2"),
185 // ModTrackPanelFN( OnFuncReplaceTrackPanel ) );
186 }
187 break;
188 default:
189 break;
190 }
191
192 return 1;
193 }
194
195 //Example code commented out.
196 #if 1
197 // This is an example function to hijack the main panel
198 int MOD_TRACK_PANEL_DLL_API MainPanelFunc(int ix)
199 {
200 ix=ix;//compiler food
201 // If we wanted to hide Audacity's Project,
202 // we'd create a new wxFrame right here and return a pointer to it
203 // as our return result.
204
205 // Don't hijack the main panel, just return a NULL;
206 return NULL;
207 }
208 #endif
209
210
211 } // End extern "C"
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 ModTrackPanelCallback.cpp
5
6 James Crook
7
8 Audacity is free software.
9 This file is licensed under the wxWidgets license, see License.txt
10
11 ********************************************************************//**
12
13 \class ModTrackPanelCallback
14 \brief ModTrackPanelCallback is a class containing all the callback
15 functions for the second generation TrackPanel. These functions are
16 added into the standard Audacity Project Menus.
17
18 *//*****************************************************************//**
19
20 \class ModTrackPanelCommandFunctor
21 \brief We create one of these functors for each menu item or
22 command which we register with the Command Manager. These take the
23 click from the menu into the actual function to be called.
24
25 *//********************************************************************/
26
27 #include <wx/wx.h>
28 #include "ModTrackPanelCallback.h"
29 #include "Audacity.h"
30 #include "ShuttleGui.h"
31 #include "Project.h"
32 #include "LoadModules.h"
33 #include "Registrar.h"
34 #include "TrackPanel2.h"
35
36 /*
37 There are several functions that can be used in a GUI module.
38
39 //#define versionFnName "GetVersionString"
40 If the version is wrong, the module will be rejected.
41 That is it will be loaded and then unloaded.
42
43 //#define ModuleDispatchName "ModuleDispatch"
44 The most useful function. See the example in this
45 file. It has several cases/options in it.
46
47 //#define scriptFnName "RegScriptServerFunc"
48 This function is run from a non gui thread. It was originally
49 created for the benefit of mod-script-pipe.
50
51 //#define mainPanelFnName "MainPanelFunc"
52 This function is the hijacking function, to take over Audacity
53 and replace the main project window with our own wxFrame.
54
55 */
56
57 // The machinery here is somewhat overkill for what we need.
58 // It allows us to add lots of menu and other actions into Audacity.
59 // We need to jump through these hoops even if only adding
60 // two menu items into Audacity.
61
62 // The OnFunc functrions are functions which can be invoked
63 // by Audacity. Mostly they are for menu items. They could
64 // be for buttons. They could be for commands invokable by
65 // script (but no examples of that yet).
66 class ModTrackPanelCallback
67 {
68 public:
69 void OnFuncShowAudioExplorer();
70 void OnFuncReplaceTrackPanel();
71 };
72
73 typedef void (ModTrackPanelCallback::*ModTrackPanelCommandFunction)();
74
75 // We have an instance of this CommandFunctor for each
76 // instance of a command we attach to Audacity.
77 // Although the commands have void argument,
78 // they do receive an instance of ModTrackPanelCallback as a 'this',
79 // so if we want to, we can pass data to each function instance.
80 class ModTrackPanelCommandFunctor:public CommandFunctor
81 {
82 public:
83 ModTrackPanelCommandFunctor(ModTrackPanelCallback *pData,
84 ModTrackPanelCommandFunction pFunction);
85 virtual void operator()(int index = 0, const wxEvent * evt=NULL);
86 public:
87 ModTrackPanelCallback * mpData;
88 ModTrackPanelCommandFunction mpFunction;
89 };
90
91 // If pData is NULL we will later be passing a NULL 'this' to pFunction.
92 // This may be quite OK if the class function is written as if it
93 // could be static.
94 ModTrackPanelCommandFunctor::ModTrackPanelCommandFunctor(ModTrackPanelCallback *pData,
95 ModTrackPanelCommandFunction pFunction)
96 {
97 mpData = pData;
98 mpFunction = pFunction;
99 }
100
101 // The dispatching function in the command functor.
102 void ModTrackPanelCommandFunctor::operator()(int index, const wxEvent * WXUNUSED(evt) )
103 {
104 (mpData->*(mpFunction))();
105 }
106
107 #define ModTrackPanelFN(X) new ModTrackPanelCommandFunctor(pModTrackPanelCallback, \
108 (ModTrackPanelCommandFunction)(&ModTrackPanelCallback::X))
109
110
111 void ModTrackPanelCallback::OnFuncShowAudioExplorer()
112 {
113 int k=3;
114 Registrar::ShowNewPanel();
115 }
116
117 void ModTrackPanelCallback::OnFuncReplaceTrackPanel()
118 {
119 // Upgrade the factory. Now all TrackPanels will be created as TrackPanel 2's
120
121 #if 0
122 AudacityProject *p = GetActiveProject();
123 wxASSERT( p!= NULL );
124 // Change it's type (No new storage allocated).
125 TrackPanel2::Upgrade( &p->mTrackPanel );
126 int k=4;
127 #endif
128 }
129
130 // Oooh look, we're using a NULL object, and hence a NULL 'this'.
131 // That's OK.
132 ModTrackPanelCallback * pModTrackPanelCallback=NULL;
133
134 //This is the DLL related machinery that actually gets called by Audacity
135 //as prt of loading and using a DLL.
136 //It is MUCH simpler to use C for this interface because then the
137 //function names are not 'mangled'.
138 //The function names are important, because they are what Audacity looks
139 //for. Change the name and they won't be found.
140 //Change the signature, e.g. return type, and you probably have a crash.
141 extern "C" {
142 // GetVersionString
143 // REQUIRED for the module to be accepted by Audacity.
144 // Without it Audacity will see a version number mismatch.
145 MOD_TRACK_PANEL_DLL_API wxChar * GetVersionString()
146 {
147 // Make sure that this version of the module requires the version
148 // of Audacity it is built with.
149 // For now, the versions must match exactly for Audacity to
150 // agree to load the module.
151 return AUDACITY_VERSION_STRING;
152 }
153
154 // This is the function that connects us to Audacity.
155 MOD_TRACK_PANEL_DLL_API int ModuleDispatch(ModuleDispatchTypes type)
156 {
157 switch (type)
158 {
159 case AppInitialized:
160 Registrar::Start();
161 // Demand that all track panels be created using the TrackPanel2Factory.
162 TrackPanel::FactoryFunction = TrackPanel2Factory;
163 break;
164 case AppQuiting:
165 Registrar::Finish();
166 break;
167 case ProjectInitialized:
168 case MenusRebuilt:
169 {
170 AudacityProject *p = GetActiveProject();
171 if( p== NULL )
172 return 0;
173
174 wxMenuBar * pBar = p->GetMenuBar();
175 wxMenu * pMenu = pBar->GetMenu( 7 ); // Menu 7 is the Analyze Menu.
176 CommandManager * c = p->GetCommandManager();
177
178 c->SetToMenu( pMenu );
179 c->AddSeparator();
180 // We add two new commands into the Analyze menu.
181 c->AddItem( _T("Extra Dialog..."), _T("Experimental Extra Dialog for whatever you want."),
182 ModTrackPanelFN( OnFuncShowAudioExplorer ) );
183 //Second menu tweak no longer needed as we always make TrackPanel2's.
184 //c->AddItem( _T("Replace TrackPanel..."), _T("Replace Current TrackPanel with TrackPanel2"),
185 // ModTrackPanelFN( OnFuncReplaceTrackPanel ) );
186 }
187 break;
188 default:
189 break;
190 }
191
192 return 1;
193 }
194
195 //Example code commented out.
196 #if 1
197 // This is an example function to hijack the main panel
198 int MOD_TRACK_PANEL_DLL_API MainPanelFunc(int ix)
199 {
200 ix=ix;//compiler food
201 // If we wanted to hide Audacity's Project,
202 // we'd create a new wxFrame right here and return a pointer to it
203 // as our return result.
204
205 // Don't hijack the main panel, just return a NULL;
206 return NULL;
207 }
208 #endif
209
210
211 } // End extern "C"
0 // The following ifdef block is the standard way of creating macros which make exporting
1 // from a DLL simpler. All files within this DLL are compiled with the LIBSCRIPT_EXPORTS
2 // symbol defined on the command line. this symbol should not be defined on any project
3 // that uses this DLL. This way any other project whose source files include this file see
4 // MOD_TRACK_PANEL_DLL_API functions as being imported from a DLL, wheras this DLL sees symbols
5 // defined with this macro as being exported.
6
7
8 /* Magic for dynamic library import and export. This is unfortunately
9 * compiler-specific because there isn't a standard way to do it. Currently it
10 * works with the Visual Studio compiler for windows, and for GCC 4+. Anything
11 * else gets all symbols made public, which gets messy */
12 /* The Visual Studio implementation */
13 #ifdef _MSC_VER
14 #define MOD_TRACK_PANEL_DLL_IMPORT _declspec(dllimport)
15 #ifdef BUILDING_MOD_TRACK_PANEL
16 #define MOD_TRACK_PANEL_DLL_API _declspec(dllexport)
17 #elif _DLL
18 #define MOD_TRACK_PANEL_DLL_API _declspec(dllimport)
19 #else
20 #define AUDACITY_DLL_API
21 #endif
22 #endif //_MSC_VER
23
24 /* The GCC implementation */
25 #ifdef CC_HASVISIBILITY // this is provided by the configure script, is only
26 // enabled for suitable GCC versions
27 /* The incantation is a bit weird here because it uses ELF symbol stuff. If we
28 * make a symbol "default" it makes it visible (for import or export). Making it
29 * "hidden" means it is invisible outside the shared object. */
30 #define MOD_TRACK_PANEL_DLL_IMPORT __attribute__((visibility("default")))
31 #ifdef BUILDING_MOD_TRACK_PANEL
32 #define MOD_TRACK_PANEL_DLL_API __attribute__((visibility("default")))
33 #else
34 #define MOD_TRACK_PANEL_DLL_API __attribute__((visibility("default")))
35 #endif
36 #endif
37
0 // The following ifdef block is the standard way of creating macros which make exporting
1 // from a DLL simpler. All files within this DLL are compiled with the LIBSCRIPT_EXPORTS
2 // symbol defined on the command line. this symbol should not be defined on any project
3 // that uses this DLL. This way any other project whose source files include this file see
4 // MOD_TRACK_PANEL_DLL_API functions as being imported from a DLL, wheras this DLL sees symbols
5 // defined with this macro as being exported.
6
7
8 /* Magic for dynamic library import and export. This is unfortunately
9 * compiler-specific because there isn't a standard way to do it. Currently it
10 * works with the Visual Studio compiler for windows, and for GCC 4+. Anything
11 * else gets all symbols made public, which gets messy */
12 /* The Visual Studio implementation */
13 #ifdef _MSC_VER
14 #define MOD_TRACK_PANEL_DLL_IMPORT _declspec(dllimport)
15 #ifdef BUILDING_MOD_TRACK_PANEL
16 #define MOD_TRACK_PANEL_DLL_API _declspec(dllexport)
17 #elif _DLL
18 #define MOD_TRACK_PANEL_DLL_API _declspec(dllimport)
19 #else
20 #define AUDACITY_DLL_API
21 #endif
22 #endif //_MSC_VER
23
24 /* The GCC implementation */
25 #ifdef CC_HASVISIBILITY // this is provided by the configure script, is only
26 // enabled for suitable GCC versions
27 /* The incantation is a bit weird here because it uses ELF symbol stuff. If we
28 * make a symbol "default" it makes it visible (for import or export). Making it
29 * "hidden" means it is invisible outside the shared object. */
30 #define MOD_TRACK_PANEL_DLL_IMPORT __attribute__((visibility("default")))
31 #ifdef BUILDING_MOD_TRACK_PANEL
32 #define MOD_TRACK_PANEL_DLL_API __attribute__((visibility("default")))
33 #else
34 #define MOD_TRACK_PANEL_DLL_API __attribute__((visibility("default")))
35 #endif
36 #endif
37
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 Registrar.cpp
5
6 James Crook
7
8 Audacity is free software.
9 This file is licensed under the wxWidgets license, see License.txt
10
11 ********************************************************************//**
12
13 \class Registrar
14 \brief Registrar is a class that other classes register resources of
15 various kinds with. It makes a system that is much more amenable to
16 plugging in of new resources.
17
18 *//********************************************************************/
19
20 #include <wx/wx.h>
21 #include "Registrar.h"
22
23 Registrar * pRegistrar = NULL;
24
25 // By defining the external function and including it here, we save ourselves maintaing two lists.
26 // Also we save ourselves recompiling Registrar each time the classes that regiser change.
27 // Part of the idea is that the Registrar knows very little about the classes that
28 // register with it.
29 #define DISPATCH( Name ) extern int Name##Dispatch( Registrar & R, t_RegistrarDispatchType Type );\
30 Name##Dispatch( *pRegistrar, Type )
31
32 // Not a class function, otherwise the functions called
33 // are treated as belonging to the class.
34 int RegistrarDispatch( t_RegistrarDispatchType Type )
35 {
36 wxASSERT( pRegistrar != NULL );
37
38 DISPATCH( SkewedRuler );
39 DISPATCH( MidiArtist );
40 DISPATCH( WaveArtist );
41 DISPATCH( EnvelopeArtist );
42 DISPATCH( LabelArtist );
43 DISPATCH( DragGridSizer );
44 DISPATCH( TrackPanel2 );
45 return 0;
46 }
47
48 // Start()
49 // Static function. Allocates Registrar.
50 void Registrar::Start()
51 {
52 wxASSERT( pRegistrar ==NULL );
53 pRegistrar = new Registrar();
54
55 RegistrarDispatch( RegResource );
56 RegistrarDispatch( RegArtist );
57 RegistrarDispatch( RegDataType );
58 RegistrarDispatch( RegCommand );
59 RegistrarDispatch( RegMenuItem );
60 }
61
62 // Finish()
63 // Static function. DeAllocates Registrar.
64 void Registrar::Finish()
65 {
66 wxASSERT( pRegistrar !=NULL );
67 delete pRegistrar;
68 pRegistrar = NULL;
69 }
70
71 void Registrar::ShowNewPanel()
72 {
73 wxASSERT( pRegistrar !=NULL );
74 if( pRegistrar->pShowFn != NULL)
75 pRegistrar->pShowFn();
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 Registrar.cpp
5
6 James Crook
7
8 Audacity is free software.
9 This file is licensed under the wxWidgets license, see License.txt
10
11 ********************************************************************//**
12
13 \class Registrar
14 \brief Registrar is a class that other classes register resources of
15 various kinds with. It makes a system that is much more amenable to
16 plugging in of new resources.
17
18 *//********************************************************************/
19
20 #include <wx/wx.h>
21 #include "Registrar.h"
22
23 Registrar * pRegistrar = NULL;
24
25 // By defining the external function and including it here, we save ourselves maintaing two lists.
26 // Also we save ourselves recompiling Registrar each time the classes that regiser change.
27 // Part of the idea is that the Registrar knows very little about the classes that
28 // register with it.
29 #define DISPATCH( Name ) extern int Name##Dispatch( Registrar & R, t_RegistrarDispatchType Type );\
30 Name##Dispatch( *pRegistrar, Type )
31
32 // Not a class function, otherwise the functions called
33 // are treated as belonging to the class.
34 int RegistrarDispatch( t_RegistrarDispatchType Type )
35 {
36 wxASSERT( pRegistrar != NULL );
37
38 DISPATCH( SkewedRuler );
39 DISPATCH( MidiArtist );
40 DISPATCH( WaveArtist );
41 DISPATCH( EnvelopeArtist );
42 DISPATCH( LabelArtist );
43 DISPATCH( DragGridSizer );
44 DISPATCH( TrackPanel2 );
45 return 0;
46 }
47
48 // Start()
49 // Static function. Allocates Registrar.
50 void Registrar::Start()
51 {
52 wxASSERT( pRegistrar ==NULL );
53 pRegistrar = new Registrar();
54
55 RegistrarDispatch( RegResource );
56 RegistrarDispatch( RegArtist );
57 RegistrarDispatch( RegDataType );
58 RegistrarDispatch( RegCommand );
59 RegistrarDispatch( RegMenuItem );
60 }
61
62 // Finish()
63 // Static function. DeAllocates Registrar.
64 void Registrar::Finish()
65 {
66 wxASSERT( pRegistrar !=NULL );
67 delete pRegistrar;
68 pRegistrar = NULL;
69 }
70
71 void Registrar::ShowNewPanel()
72 {
73 wxASSERT( pRegistrar !=NULL );
74 if( pRegistrar->pShowFn != NULL)
75 pRegistrar->pShowFn();
7676 }
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 Registrar.h
5
6 James Crook
7
8 Manages centralised registration of resources.
9
10 **********************************************************************/
11
12 #ifndef __AUDACITY_REGISTRAR__
13 #define __AUDACITY_REGISTRAR__
14
15 typedef enum
16 {
17 RegResource,
18 RegArtist,
19 RegDataType,
20 RegCommand,
21 RegMenuItem,
22 RegLast
23 } t_RegistrarDispatchType;
24
25 class Registrar {
26 Registrar::Registrar(){
27 pShowFn = NULL;}
28 public:
29 // Fairly generic registrar functions.
30 static void Start();
31 static void Finish();
32
33 // Somewhat specific to this application registrar functions.
34 // These mostly reflect one-offs, where a more sophisticated
35 // system would manage a list.
36 static void ShowNewPanel();
37 public:
38 void (*pShowFn)(void);
39 };
40
41
42 extern int RegistrarDispatch( t_RegistrarDispatchType Type );
43
44 #endif
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 Registrar.h
5
6 James Crook
7
8 Manages centralised registration of resources.
9
10 **********************************************************************/
11
12 #ifndef __AUDACITY_REGISTRAR__
13 #define __AUDACITY_REGISTRAR__
14
15 typedef enum
16 {
17 RegResource,
18 RegArtist,
19 RegDataType,
20 RegCommand,
21 RegMenuItem,
22 RegLast
23 } t_RegistrarDispatchType;
24
25 class Registrar {
26 Registrar::Registrar(){
27 pShowFn = NULL;}
28 public:
29 // Fairly generic registrar functions.
30 static void Start();
31 static void Finish();
32
33 // Somewhat specific to this application registrar functions.
34 // These mostly reflect one-offs, where a more sophisticated
35 // system would manage a list.
36 static void ShowNewPanel();
37 public:
38 void (*pShowFn)(void);
39 };
40
41
42 extern int RegistrarDispatch( t_RegistrarDispatchType Type );
43
44 #endif
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 Registrar.cpp
5
6 James Crook
7
8 Audacity is free software.
9 This file is licensed under the wxWidgets license, see License.txt
10
11 ********************************************************************//**
12
13 \class SkewedRuller
14 \brief SkewedRuler draws a ruler for aligning two sequences.
15
16 *//********************************************************************/
17
18 #include <wx/wx.h>
19 #include "Registrar.h"
20 #include "SkewedRuler.h"
21
22 extern int SkewedRulerDispatch( Registrar & R, t_RegistrarDispatchType Type )
23 {
24 switch( Type )
25 {
26 case RegArtist:
27 break;
28 case RegDataType:
29 break;
30 case RegCommand:
31 break;
32 case RegMenuItem:
33 break;
34 default:
35 break;
36 }
37 return 1;
38 }
39
40
41 // For now I've bunged these empty dispatch functions into the same
42 // file as SkewedRuler.
43 // When I am ready to work on them I will create new files for them.
44 int MidiArtistDispatch( Registrar & R, t_RegistrarDispatchType Type )
45 {
46 switch( Type )
47 {
48 case RegArtist:
49 break;
50 case RegDataType:
51 break;
52 case RegCommand:
53 break;
54 case RegMenuItem:
55 break;
56 default:
57 break;
58 }
59 return 1;
60 }
61
62 extern int WaveArtistDispatch( Registrar & R, t_RegistrarDispatchType Type )
63 {
64 switch( Type )
65 {
66 case RegArtist:
67 break;
68 case RegDataType:
69 break;
70 case RegCommand:
71 break;
72 case RegMenuItem:
73 break;
74 default:
75 break;
76 }
77 return 1;
78 }
79
80 int EnvelopeArtistDispatch( Registrar & R, t_RegistrarDispatchType Type )
81 {
82 switch( Type )
83 {
84 case RegArtist:
85 break;
86 case RegDataType:
87 break;
88 case RegCommand:
89 break;
90 case RegMenuItem:
91 break;
92 default:
93 break;
94 }
95 return 1;
96 }
97
98 int LabelArtistDispatch( Registrar & R, t_RegistrarDispatchType Type )
99 {
100 switch( Type )
101 {
102 case RegArtist:
103 break;
104 case RegDataType:
105 break;
106 case RegCommand:
107 break;
108 case RegMenuItem:
109 break;
110 default:
111 break;
112 }
113 return 1;
114 }
115
116 int DragGridSizerDispatch( Registrar & R, t_RegistrarDispatchType Type )
117 {
118 switch( Type )
119 {
120 case RegArtist:
121 break;
122 case RegDataType:
123 break;
124 case RegCommand:
125 break;
126 case RegMenuItem:
127 break;
128 default:
129 break;
130 }
131 return 1;
132 }
133
134
135
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 Registrar.cpp
5
6 James Crook
7
8 Audacity is free software.
9 This file is licensed under the wxWidgets license, see License.txt
10
11 ********************************************************************//**
12
13 \class SkewedRuller
14 \brief SkewedRuler draws a ruler for aligning two sequences.
15
16 *//********************************************************************/
17
18 #include <wx/wx.h>
19 #include "Registrar.h"
20 #include "SkewedRuler.h"
21
22 extern int SkewedRulerDispatch( Registrar & R, t_RegistrarDispatchType Type )
23 {
24 switch( Type )
25 {
26 case RegArtist:
27 break;
28 case RegDataType:
29 break;
30 case RegCommand:
31 break;
32 case RegMenuItem:
33 break;
34 default:
35 break;
36 }
37 return 1;
38 }
39
40
41 // For now I've bunged these empty dispatch functions into the same
42 // file as SkewedRuler.
43 // When I am ready to work on them I will create new files for them.
44 int MidiArtistDispatch( Registrar & R, t_RegistrarDispatchType Type )
45 {
46 switch( Type )
47 {
48 case RegArtist:
49 break;
50 case RegDataType:
51 break;
52 case RegCommand:
53 break;
54 case RegMenuItem:
55 break;
56 default:
57 break;
58 }
59 return 1;
60 }
61
62 extern int WaveArtistDispatch( Registrar & R, t_RegistrarDispatchType Type )
63 {
64 switch( Type )
65 {
66 case RegArtist:
67 break;
68 case RegDataType:
69 break;
70 case RegCommand:
71 break;
72 case RegMenuItem:
73 break;
74 default:
75 break;
76 }
77 return 1;
78 }
79
80 int EnvelopeArtistDispatch( Registrar & R, t_RegistrarDispatchType Type )
81 {
82 switch( Type )
83 {
84 case RegArtist:
85 break;
86 case RegDataType:
87 break;
88 case RegCommand:
89 break;
90 case RegMenuItem:
91 break;
92 default:
93 break;
94 }
95 return 1;
96 }
97
98 int LabelArtistDispatch( Registrar & R, t_RegistrarDispatchType Type )
99 {
100 switch( Type )
101 {
102 case RegArtist:
103 break;
104 case RegDataType:
105 break;
106 case RegCommand:
107 break;
108 case RegMenuItem:
109 break;
110 default:
111 break;
112 }
113 return 1;
114 }
115
116 int DragGridSizerDispatch( Registrar & R, t_RegistrarDispatchType Type )
117 {
118 switch( Type )
119 {
120 case RegArtist:
121 break;
122 case RegDataType:
123 break;
124 case RegCommand:
125 break;
126 case RegMenuItem:
127 break;
128 default:
129 break;
130 }
131 return 1;
132 }
133
134
135
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 SkewedRuler.h
5
6 James Crook
7
8 Draws a ruler used for aligning two time sequences.
9
10 **********************************************************************/
11
12 #ifndef __AUDACITY_SKEWED_RULER__
13 #define __AUDACITY_SKEWED_RULER__
14
15
16
17 class SkewedRuler {
18 public:
19
20 };
21
22 #endif
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 SkewedRuler.h
5
6 James Crook
7
8 Draws a ruler used for aligning two time sequences.
9
10 **********************************************************************/
11
12 #ifndef __AUDACITY_SKEWED_RULER__
13 #define __AUDACITY_SKEWED_RULER__
14
15
16
17 class SkewedRuler {
18 public:
19
20 };
21
22 #endif
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 Registrar.cpp
5
6 James Crook
7
8 Audacity is free software.
9 This file is licensed under the wxWidgets license, see License.txt
10
11 ********************************************************************//**
12
13 \class TrackPanel2
14 \brief TrackPanel2 is the start of the new TrackPanel.
15
16 *//********************************************************************/
17
18 #include <wx/wx.h>
19 #include "ShuttleGui.h"
20 #include "widgets/LinkingHtmlWindow.h"
21 #include "SkewedRuler.h"
22 #include "Registrar.h"
23 #include "TrackPanel2.h"
24
25 TrackPanel * TrackPanel2Factory(wxWindow * parent,
26 wxWindowID id,
27 const wxPoint & pos,
28 const wxSize & size,
29 TrackList * tracks,
30 ViewInfo * viewInfo,
31 TrackPanelListener * listener,
32 AdornedRulerPanel * ruler)
33 {
34 return new TrackPanel2(
35 parent,
36 id,
37 pos,
38 size,
39 tracks,
40 viewInfo,
41 listener,
42 ruler);
43 }
44
45 void ShowExtraDialog()
46 {
47 int k=42;
48
49 wxDialog Dlg(NULL, wxID_ANY, wxString(wxT("Experimental Extra Dialog")));
50 ShuttleGui S(&Dlg, eIsCreating);
51 S.StartNotebook();
52 {
53 S.StartNotebookPage( _("Panel 1") );
54 S.StartVerticalLay(1);
55 {
56 HtmlWindow *html = new LinkingHtmlWindow(S.GetParent(), -1,
57 wxDefaultPosition,
58 wxSize(600, 359),
59 wxHW_SCROLLBAR_AUTO | wxSUNKEN_BORDER);
60 html->SetFocus();
61 html->SetPage(wxT("<h1><font color=\"blue\">An Html Window</font></h1>Replace with whatever you like."));
62 S.Prop(1).AddWindow( html, wxEXPAND );
63 }
64 S.EndVerticalLay();
65 S.EndNotebookPage();
66
67 S.StartNotebookPage( _("Diagnostics") );
68 S.StartVerticalLay(1);
69 {
70 HtmlWindow *html = new LinkingHtmlWindow(S.GetParent(), -1,
71 wxDefaultPosition,
72 wxSize(600, 359),
73 wxHW_SCROLLBAR_AUTO | wxSUNKEN_BORDER);
74 html->SetFocus();
75 html->SetPage(wxT("<h1>Diagnostics</h1>This is an html diagnostics page"));
76 S.Prop(1).AddWindow( html, wxEXPAND );
77 }
78 S.EndVerticalLay();
79 S.EndNotebookPage();
80 }
81 S.EndNotebook();
82
83 wxButton *ok = new wxButton(S.GetParent(), wxID_OK, _("OK... Audacious!"));
84 ok->SetDefault();
85 S.Prop(0).AddWindow( ok );
86
87 Dlg.Fit();
88
89 Dlg.ShowModal();
90 }
91
92
93 int TrackPanel2Dispatch( Registrar & R, t_RegistrarDispatchType Type )
94 {
95 switch( Type )
96 {
97 case RegResource:
98 R.pShowFn = ShowExtraDialog;
99 break;
100 case RegArtist:
101 break;
102 case RegDataType:
103 break;
104 case RegCommand:
105 break;
106 case RegMenuItem:
107 break;
108 default:
109 break;
110 }
111 return 1;
112 }
113
114 TrackPanel2::TrackPanel2(
115 wxWindow * parent, wxWindowID id, const wxPoint & pos, const wxSize & size,
116 TrackList * tracks, ViewInfo * viewInfo, TrackPanelListener * listener,
117 AdornedRulerPanel * ruler) :
118 TrackPanel(
119 parent, id, pos, size,
120 tracks, viewInfo, listener, ruler)
121 {
122 }
123
124
125 // Here is a sample function that shows that TrackPanel2 is being invoked.
126 void TrackPanel2::OnPaint(wxPaintEvent & event)
127 {
128 // Hmm... Log debug will only show if you open the log window.
129 // wxLogDebug( wxT("Paint TrackPanel2 requested") );
130 TrackPanel::OnPaint( event );
131 }
132
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 Registrar.cpp
5
6 James Crook
7
8 Audacity is free software.
9 This file is licensed under the wxWidgets license, see License.txt
10
11 ********************************************************************//**
12
13 \class TrackPanel2
14 \brief TrackPanel2 is the start of the new TrackPanel.
15
16 *//********************************************************************/
17
18 #include <wx/wx.h>
19 #include "ShuttleGui.h"
20 #include "widgets/LinkingHtmlWindow.h"
21 #include "SkewedRuler.h"
22 #include "Registrar.h"
23 #include "TrackPanel2.h"
24
25 TrackPanel * TrackPanel2Factory(wxWindow * parent,
26 wxWindowID id,
27 const wxPoint & pos,
28 const wxSize & size,
29 TrackList * tracks,
30 ViewInfo * viewInfo,
31 TrackPanelListener * listener,
32 AdornedRulerPanel * ruler)
33 {
34 return new TrackPanel2(
35 parent,
36 id,
37 pos,
38 size,
39 tracks,
40 viewInfo,
41 listener,
42 ruler);
43 }
44
45 void ShowExtraDialog()
46 {
47 int k=42;
48
49 wxDialog Dlg(NULL, wxID_ANY, wxString(wxT("Experimental Extra Dialog")));
50 ShuttleGui S(&Dlg, eIsCreating);
51 S.StartNotebook();
52 {
53 S.StartNotebookPage( _("Panel 1") );
54 S.StartVerticalLay(1);
55 {
56 HtmlWindow *html = new LinkingHtmlWindow(S.GetParent(), -1,
57 wxDefaultPosition,
58 wxSize(600, 359),
59 wxHW_SCROLLBAR_AUTO | wxSUNKEN_BORDER);
60 html->SetFocus();
61 html->SetPage(wxT("<h1><font color=\"blue\">An Html Window</font></h1>Replace with whatever you like."));
62 S.Prop(1).AddWindow( html, wxEXPAND );
63 }
64 S.EndVerticalLay();
65 S.EndNotebookPage();
66
67 S.StartNotebookPage( _("Diagnostics") );
68 S.StartVerticalLay(1);
69 {
70 HtmlWindow *html = new LinkingHtmlWindow(S.GetParent(), -1,
71 wxDefaultPosition,
72 wxSize(600, 359),
73 wxHW_SCROLLBAR_AUTO | wxSUNKEN_BORDER);
74 html->SetFocus();
75 html->SetPage(wxT("<h1>Diagnostics</h1>This is an html diagnostics page"));
76 S.Prop(1).AddWindow( html, wxEXPAND );
77 }
78 S.EndVerticalLay();
79 S.EndNotebookPage();
80 }
81 S.EndNotebook();
82
83 wxButton *ok = new wxButton(S.GetParent(), wxID_OK, _("OK... Audacious!"));
84 ok->SetDefault();
85 S.Prop(0).AddWindow( ok );
86
87 Dlg.Fit();
88
89 Dlg.ShowModal();
90 }
91
92
93 int TrackPanel2Dispatch( Registrar & R, t_RegistrarDispatchType Type )
94 {
95 switch( Type )
96 {
97 case RegResource:
98 R.pShowFn = ShowExtraDialog;
99 break;
100 case RegArtist:
101 break;
102 case RegDataType:
103 break;
104 case RegCommand:
105 break;
106 case RegMenuItem:
107 break;
108 default:
109 break;
110 }
111 return 1;
112 }
113
114 TrackPanel2::TrackPanel2(
115 wxWindow * parent, wxWindowID id, const wxPoint & pos, const wxSize & size,
116 TrackList * tracks, ViewInfo * viewInfo, TrackPanelListener * listener,
117 AdornedRulerPanel * ruler) :
118 TrackPanel(
119 parent, id, pos, size,
120 tracks, viewInfo, listener, ruler)
121 {
122 }
123
124
125 // Here is a sample function that shows that TrackPanel2 is being invoked.
126 void TrackPanel2::OnPaint(wxPaintEvent & event)
127 {
128 // Hmm... Log debug will only show if you open the log window.
129 // wxLogDebug( wxT("Paint TrackPanel2 requested") );
130 TrackPanel::OnPaint( event );
131 }
132
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 TrackPanel2.h
5
6 James Crook
7
8 **********************************************************************/
9
10 #ifndef __AUDACITY_TRACK_PANEL2__
11 #define __AUDACITY_TRACK_PANEL2__
12
13 #include "TrackPanel.h"
14
15 class TrackPanel2 : public TrackPanel
16 {
17 public:
18 TrackPanel2(
19 wxWindow * parent, wxWindowID id,
20 const wxPoint & pos,
21 const wxSize & size,
22 TrackList * tracks,
23 ViewInfo * viewInfo,
24 TrackPanelListener * listener,
25 AdornedRulerPanel * ruler);
26
27 // Upgrades an existing TrackPanel to a TrackPanel2
28 static void Upgrade( TrackPanel ** ppTrackPanel );
29
30 virtual void OnPaint(wxPaintEvent & event);
31 };
32
33 // Factory function.
34 TrackPanel * TrackPanel2Factory(wxWindow * parent,
35 wxWindowID id,
36 const wxPoint & pos,
37 const wxSize & size,
38 TrackList * tracks,
39 ViewInfo * viewInfo,
40 TrackPanelListener * listener,
41 AdornedRulerPanel * ruler);
42
43 #endif
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 TrackPanel2.h
5
6 James Crook
7
8 **********************************************************************/
9
10 #ifndef __AUDACITY_TRACK_PANEL2__
11 #define __AUDACITY_TRACK_PANEL2__
12
13 #include "TrackPanel.h"
14
15 class TrackPanel2 : public TrackPanel
16 {
17 public:
18 TrackPanel2(
19 wxWindow * parent, wxWindowID id,
20 const wxPoint & pos,
21 const wxSize & size,
22 TrackList * tracks,
23 ViewInfo * viewInfo,
24 TrackPanelListener * listener,
25 AdornedRulerPanel * ruler);
26
27 // Upgrades an existing TrackPanel to a TrackPanel2
28 static void Upgrade( TrackPanel ** ppTrackPanel );
29
30 virtual void OnPaint(wxPaintEvent & event);
31 };
32
33 // Factory function.
34 TrackPanel * TrackPanel2Factory(wxWindow * parent,
35 wxWindowID id,
36 const wxPoint & pos,
37 const wxSize & size,
38 TrackList * tracks,
39 ViewInfo * viewInfo,
40 TrackPanelListener * listener,
41 AdornedRulerPanel * ruler);
42
43 #endif
0 <?xml version="1.0" encoding="UTF-8"?>
1 <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
2 <mime-type type="application/x-audacity-project">
3 <sub-class-of type="text/xml"/>
4 <comment>Audacity project</comment>
5 <glob pattern="*.aup"/>
6 </mime-type>
7 </mime-info>
0 <?xml version="1.0" encoding="UTF-8"?>
1 <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
2 <mime-type type="application/x-audacity-project">
3 <sub-class-of type="text/xml"/>
4 <comment>Audacity project</comment>
5 <glob pattern="*.aup"/>
6 </mime-type>
7 </mime-info>
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 Effect/ScienFilter.cpp
5
6 Norm C
7 Mitch Golden
8 Vaughan Johnson (Preview)
9
10 *******************************************************************//**
11
12 \file ScienFilter.cpp
13 \brief Implements EffectScienFilter, ScienFilterDialog,
14 ScienFilterPanel.
15
16 *//****************************************************************//**
17
18 \class EffectScienFilter
19 \brief An Effect.
20
21 Performs IIR filtering that emulates analog filters, specifically
22 Butterworth, Chebyshev Type I and Type II. Highpass and lowpass filters
23 are supported, as are filter orders from 1 to 10.
24
25 The filter is applied using biquads
26
27 *//****************************************************************//**
28
29 \class ScienFilterDialog
30 \brief Dialog used with EffectScienFilter
31
32 *//****************************************************************//**
33
34 \class ScienFilterPanel
35 \brief ScienFilterPanel is used with ScienFilterDialog and controls
36 a graph for EffectScienFilter.
37
38 *//*******************************************************************/
39
40 #include "../Audacity.h"
41 #include "ScienFilter.h"
42 #include "Equalization.h" // For SliderAx
43 #include "../AColor.h"
44 #include "../ShuttleGui.h"
45 #include "../PlatformCompatibility.h"
46 #include "../Prefs.h"
47 #include "../Project.h"
48 #include "../WaveTrack.h"
49 #include "../widgets/Ruler.h"
50 #include "../Theme.h"
51 #include "../AllThemeResources.h"
52 #include "../WaveTrack.h"
53 #include "float_cast.h"
54
55 #include <wx/bitmap.h>
56 #include <wx/msgdlg.h>
57 #include <wx/brush.h>
58 #include <wx/dcmemory.h>
59 #include <wx/event.h>
60 #include <wx/image.h>
61 #include <wx/intl.h>
62 #include <wx/stattext.h>
63 #include <wx/string.h>
64 #include <wx/textdlg.h>
65 #include <wx/stdpaths.h>
66 #include <wx/settings.h>
67
68 #if wxUSE_TOOLTIPS
69 #include <wx/tooltip.h>
70 #endif
71 #include <wx/utils.h>
72
73 #include <math.h>
74
75 #include <wx/arrimpl.cpp>
76
77 #define PI 3.1415926535
78 #define square(a) ((a)*(a))
79
80 #ifndef __min
81 #define __min(a,b) ((a) < (b) ? (a) : (b))
82 #endif
83 #ifndef __max
84 #define __max(a,b) ((a) > (b) ? (a) : (b))
85 #endif
86
87 // Local functions
88
89 void EffectScienFilter::ReadPrefs()
90 {
91 double dTemp;
92 gPrefs->Read(wxT("/SciFilter/Order"), &mOrder, 1);
93 mOrder = __max (1, mOrder);
94 mOrder = __min (MAX_FILTER_ORDER, mOrder);
95 gPrefs->Read(wxT("/SciFilter/FilterType"), &mFilterType, 0);
96 mFilterType = __max (0, mFilterType);
97 mFilterType = __min (2, mFilterType);
98 gPrefs->Read(wxT("/SciFilter/FilterSubtype"), &mFilterSubtype, 0);
99 mFilterSubtype = __max (0, mFilterSubtype);
100 mFilterSubtype = __min (1, mFilterSubtype);
101 gPrefs->Read(wxT("/SciFilter/Cutoff"), &dTemp, 1000.0);
102 mCutoff = (float)dTemp;
103 mCutoff = __max (1, mCutoff);
104 mCutoff = __min (100000, mCutoff);
105 gPrefs->Read(wxT("/SciFilter/Ripple"), &dTemp, 1.0);
106 mRipple = dTemp;
107 mRipple = __max (0, mRipple);
108 mRipple = __min (100, mRipple);
109 gPrefs->Read(wxT("/SciFilter/StopbandRipple"), &dTemp, 30.0);
110 mStopbandRipple = dTemp;
111 mStopbandRipple = __max (0, mStopbandRipple);
112 mStopbandRipple = __min (100, mStopbandRipple);
113 }
114
115 EffectScienFilter::EffectScienFilter()
116 {
117 ReadPrefs();
118 mPrompting = false;
119 }
120
121
122 EffectScienFilter::~EffectScienFilter()
123 {
124 }
125
126 bool EffectScienFilter::Init()
127 {
128 int selcount = 0;
129 double rate = 0.0;
130 TrackListIterator iter(GetActiveProject()->GetTracks());
131 Track *t = iter.First();
132 while (t) {
133 if (t->GetSelected() && t->GetKind() == Track::Wave) {
134 WaveTrack *track = (WaveTrack *)t;
135 if (selcount==0) {
136 rate = track->GetRate();
137 }
138 else {
139 if (track->GetRate() != rate) {
140 wxMessageBox(_("To apply a filter, all selected tracks must have the same sample rate."));
141 return(false);
142 }
143 }
144 selcount++;
145 }
146 t = iter.Next();
147 }
148 return(true);}
149
150 bool EffectScienFilter::PromptUser()
151 {
152 // Detect whether we are editing a batch chain by checking the parent window
153 mEditingBatchParams = (mParent != GetActiveProject());
154 if (!mEditingBatchParams)
155 {
156 ReadPrefs();
157 }
158
159 TrackListOfKindIterator iter(Track::Wave, mTracks);
160 WaveTrack *t = (WaveTrack *) iter.First();
161 float hiFreq;
162 if (t)
163 hiFreq = ((float)(t->GetRate())/2.);
164 else
165 hiFreq = ((float)(GetActiveProject()->GetRate())/2.);
166
167 ScienFilterDialog dlog(this, ((double)loFreqI), hiFreq, mParent, -1, _("Classic Filters"));
168
169 dlog.dBMin = mdBMin;
170 dlog.dBMax = mdBMax;
171 dlog.Order = mOrder;
172 dlog.Cutoff = mCutoff;
173 dlog.FilterType = mFilterType;
174 dlog.FilterSubtype = mFilterSubtype;
175 dlog.Ripple = mRipple;
176 dlog.StopbandRipple = mStopbandRipple;
177
178 dlog.CentreOnParent();
179
180 mPrompting = true; // true when previewing, false in batch
181 dlog.ShowModal();
182 mPrompting = false;
183
184 if (!dlog.GetReturnCode())
185 return false;
186
187 mdBMin = dlog.dBMin;
188 mdBMax = dlog.dBMax;
189 mOrder = dlog.Order;
190 mCutoff = dlog.Cutoff;
191 mFilterType = dlog.FilterType;
192 mFilterSubtype = dlog.FilterSubtype;
193 mRipple = dlog.Ripple;
194 mStopbandRipple = dlog.StopbandRipple;
195
196 if (!mEditingBatchParams)
197 {
198 // Save preferences
199 gPrefs->Write(wxT("/SciFilter/Order"), mOrder);
200 gPrefs->Write(wxT("/SciFilter/FilterType"), mFilterType);
201 gPrefs->Write(wxT("/SciFilter/FilterSubtype"), mFilterSubtype);
202 gPrefs->Write(wxT("/SciFilter/Cutoff"), mCutoff);
203 gPrefs->Write(wxT("/SciFilter/Ripple"), mRipple);
204 gPrefs->Write(wxT("/SciFilter/StopbandRipple"), mStopbandRipple);
205 gPrefs->Flush();
206 }
207
208 return true;
209 }
210
211 bool EffectScienFilter::DontPromptUser()
212 {
213 TrackListOfKindIterator iter(Track::Wave, mTracks);
214 WaveTrack *t = (WaveTrack *) iter.First();
215 float hiFreq;
216 if (t)
217 hiFreq = ((float)(t->GetRate())/2.);
218 else
219 hiFreq = ((float)(GetActiveProject()->GetRate())/2.);
220 /*i18n-hint: 'Classic Filters' is an audio effect. It's a low-pass or high-pass
221 filter with specfic characteristics. */
222 ScienFilterDialog dlog(this, ((double)loFreqI), hiFreq, NULL, -1, _("Classic Filters"));
223 dlog.dBMin = mdBMin;
224 dlog.dBMax = mdBMax;
225 dlog.Order = mOrder;
226 dlog.Cutoff = mCutoff;
227 dlog.FilterType = mFilterType;
228 dlog.FilterSubtype = mFilterSubtype;
229 dlog.Ripple = mRipple;
230
231 dlog.CalcFilter(this);
232
233 return true;
234 }
235
236 bool EffectScienFilter::TransferParameters( Shuttle & shuttle )
237 {
238 // if shuttle.mbStoreInClient is true, read prefs ScienFilter/FilterType (etc.) string and put into mFilterType (etc.)
239 // else put mFilterType (etc.) into string form and write prefs
240 shuttle.TransferInt(wxT("FilterType"),mFilterType,0);
241 shuttle.TransferInt(wxT("FilterSubtype"),mFilterSubtype,0); // etc.
242 shuttle.TransferInt(wxT("Order"),mOrder,2);
243 shuttle.TransferFloat(wxT("Cutoff"),mCutoff,1000);
244 shuttle.TransferFloat(wxT("PassbandRipple"),mRipple,1);
245 shuttle.TransferFloat(wxT("StopbandRipple"),mStopbandRipple,30);
246
247 if(!mPrompting)
248 DontPromptUser(); // not previewing, ie batch mode or initial setup
249 return true;
250 }
251
252 bool EffectScienFilter::Process()
253 {
254 this->CopyInputTracks(); // Set up mOutputTracks.
255 bool bGoodResult = true;
256
257 SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks);
258 WaveTrack *track = (WaveTrack *) iter.First();
259 int count = 0;
260 while (track)
261 {
262 double trackStart = track->GetStartTime();
263 double trackEnd = track->GetEndTime();
264 double t0 = mT0 < trackStart? trackStart: mT0;
265 double t1 = mT1 > trackEnd? trackEnd: mT1;
266
267 if (t1 > t0) {
268 sampleCount start = track->TimeToLongSamples(t0);
269 sampleCount end = track->TimeToLongSamples(t1);
270 sampleCount len = (sampleCount)(end - start);
271
272 if (!ProcessOne(count, track, start, len))
273 {
274 bGoodResult = false;
275 break;
276 }
277 }
278
279 track = (WaveTrack *) iter.Next();
280 count++;
281 }
282
283 this->ReplaceProcessedTracks(bGoodResult);
284 return bGoodResult;
285 }
286
287
288 bool EffectScienFilter::ProcessOne(int count, WaveTrack * t,
289 sampleCount start, sampleCount len)
290 {
291 // Create a new WaveTrack to hold all of the output
292 AudacityProject *p = GetActiveProject();
293 WaveTrack *output = p->GetTrackFactory()->NewWaveTrack(floatSample, t->GetRate());
294
295 sampleCount s = start;
296 sampleCount idealBlockLen = t->GetMaxBlockSize();
297 float *buffer = new float[idealBlockLen];
298 sampleCount originalLen = len;
299
300 TrackProgress(count, 0.0);
301 bool bLoopSuccess = true;
302
303 for (int iPair = 0; iPair < (mOrder+1)/2; iPair++)
304 mpBiquad [iPair]->fPrevIn = mpBiquad [iPair]->fPrevPrevIn = mpBiquad [iPair]->fPrevOut = mpBiquad [iPair]->fPrevPrevOut = 0;
305
306 while(len)
307 {
308 sampleCount block = idealBlockLen;
309 if (block > len)
310 block = len;
311
312 t->Get((samplePtr)buffer, floatSample, s, block);
313
314 for (int iPair = 0; iPair < (mOrder+1)/2; iPair++)
315 {
316 mpBiquad[iPair]->pfIn = buffer;
317 mpBiquad[iPair]->pfOut = buffer;
318 Biquad_Process (mpBiquad[iPair], block);
319 }
320 output->Append ((samplePtr)buffer, floatSample, block);
321 len -= block;
322 s += block;
323
324 if (TrackProgress (count, (s-start)/(double)originalLen))
325 {
326 bLoopSuccess = false;
327 break;
328 }
329 }
330 if (bLoopSuccess)
331 {
332 output->Flush();
333 // Now move the appropriate bit of the output back to the track
334 float *bigBuffer = new float[originalLen];
335 output->Get((samplePtr)bigBuffer, floatSample, 0, originalLen);
336 t->Set((samplePtr)bigBuffer, floatSample, start, originalLen);
337 delete[] bigBuffer;
338 }
339
340 delete[] buffer;
341 delete output;
342
343 return bLoopSuccess;
344 }
345
346 void EffectScienFilter::Filter(sampleCount WXUNUSED(len),
347 float *WXUNUSED(buffer))
348 {
349 }
350
351
352 //----------------------------------------------------------------------------
353 // ScienFilterPanel
354 //----------------------------------------------------------------------------
355
356 BEGIN_EVENT_TABLE(ScienFilterPanel, wxPanel)
357 EVT_PAINT(ScienFilterPanel::OnPaint)
358 EVT_SIZE(ScienFilterPanel::OnSize)
359 END_EVENT_TABLE()
360
361 ScienFilterPanel::ScienFilterPanel( double loFreq, double hiFreq,
362 ScienFilterDialog *parent,
363 wxWindowID id, const wxPoint& pos, const wxSize& size):
364 wxPanel(parent, id, pos, size)
365 {
366 mBitmap = NULL;
367 mWidth = 0;
368 mHeight = 0;
369 mLoFreq = loFreq;
370 mHiFreq = hiFreq;
371 mParent = parent;
372 }
373
374 ScienFilterPanel::~ScienFilterPanel()
375 {
376 if (mBitmap)
377 delete mBitmap;
378 }
379
380 void ScienFilterPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
381 {
382 Refresh( false );
383 }
384
385 void ScienFilterPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
386 {
387 wxPaintDC dc(this);
388 int width, height;
389 GetSize(&width, &height);
390
391 if (!mBitmap || mWidth!=width || mHeight!=height)
392 {
393 if (mBitmap)
394 delete mBitmap;
395
396 mWidth = width;
397 mHeight = height;
398 mBitmap = new wxBitmap(mWidth, mHeight);
399 }
400
401 wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
402
403 wxMemoryDC memDC;
404 memDC.SelectObject(*mBitmap);
405
406 wxRect bkgndRect;
407 bkgndRect.x = 0;
408 bkgndRect.y = 0;
409 bkgndRect.width = mWidth;
410 bkgndRect.height = mHeight;
411 memDC.SetBrush(bkgndBrush);
412 memDC.SetPen(*wxTRANSPARENT_PEN);
413 memDC.DrawRectangle(bkgndRect);
414
415 bkgndRect.y = mHeight;
416 memDC.DrawRectangle(bkgndRect);
417
418 wxRect border;
419 border.x = 0;
420 border.y = 0;
421 border.width = mWidth;
422 border.height = mHeight;
423
424 memDC.SetBrush(*wxWHITE_BRUSH);
425 memDC.SetPen(*wxBLACK_PEN);
426 memDC.DrawRectangle(border);
427
428 mEnvRect = border;
429 mEnvRect.Deflate(2, 2);
430
431 // Pure blue x-axis line
432 memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 1, wxSOLID));
433 int center = (int) (mEnvRect.height * dBMax/(dBMax-dBMin) + .5);
434 AColor::Line(memDC,
435 mEnvRect.GetLeft(), mEnvRect.y + center,
436 mEnvRect.GetRight(), mEnvRect.y + center);
437
438 //Now draw the actual response that you will get.
439 //mFilterFunc has a linear scale, window has a log one so we have to fiddle about
440 memDC.SetPen(wxPen(theTheme.Colour( clrResponseLines ), 3, wxSOLID));
441 double scale = (double)mEnvRect.height/(dBMax-dBMin); // pixels per dB
442 double yF; // gain at this freq
443
444 double loLog = log10(mLoFreq);
445 double step = log10(mHiFreq) - loLog;
446 step /= ((double)mEnvRect.width-1.);
447 double freq; // actual freq corresponding to x position
448 int x, y, xlast = 0, ylast = 0;
449 for(int i=0; i<mEnvRect.width; i++)
450 {
451 x = mEnvRect.x + i;
452 freq = pow(10., loLog + i*step); //Hz
453 yF = mParent->FilterMagnAtFreq (freq);
454 yF = 20*log10(yF);
455
456 if(yF < dBMin)
457 yF = dBMin;
458 yF = center-scale*yF;
459 if(yF>mEnvRect.height)
460 yF = mEnvRect.height - 1;
461 if(yF<0.)
462 yF=0.;
463 y = (int)(yF+.5);
464
465 if (i != 0 && (y < mEnvRect.height-1 || ylast < mEnvRect.y + mEnvRect.height-1))
466 {
467 AColor::Line (memDC, xlast, ylast, x, mEnvRect.y + y);
468 }
469 xlast = x;
470 ylast = mEnvRect.y + y;
471 }
472
473 memDC.SetPen(*wxBLACK_PEN);
474 mParent->freqRuler->ruler.DrawGrid(memDC, mEnvRect.height+2, true, true, 0, 1);
475 mParent->dBRuler->ruler.DrawGrid(memDC, mEnvRect.width+2, true, true, 1, 2);
476
477 dc.Blit(0, 0, mWidth, mHeight,
478 &memDC, 0, 0, wxCOPY, FALSE);
479 }
480
481
482 // WDR: class implementations
483
484 //----------------------------------------------------------------------------
485 // ScienFilterDialog
486 //----------------------------------------------------------------------------
487
488 // WDR: event table for ScienFilterDialog
489
490 BEGIN_EVENT_TABLE(ScienFilterDialog,wxDialog)
491 EVT_SIZE( ScienFilterDialog::OnSize )
492 EVT_PAINT( ScienFilterDialog::OnPaint )
493 EVT_ERASE_BACKGROUND( ScienFilterDialog::OnErase )
494
495 EVT_SLIDER( ID_DBMAX, ScienFilterDialog::OnSliderDBMAX )
496 EVT_SLIDER( ID_DBMIN, ScienFilterDialog::OnSliderDBMIN )
497 EVT_CHOICE( ID_FILTER_ORDER, ScienFilterDialog::OnOrder)
498 EVT_CHOICE( ID_FILTER_TYPE, ScienFilterDialog::OnFilterType)
499 EVT_CHOICE( ID_FILTER_SUBTYPE, ScienFilterDialog::OnFilterSubtype)
500 EVT_TEXT( ID_CUTOFF, ScienFilterDialog::OnCutoff)
501 EVT_TEXT( ID_RIPPLE, ScienFilterDialog::OnRipple)
502 EVT_TEXT( ID_STOPBAND_RIPPLE, ScienFilterDialog::OnStopbandRipple)
503
504 EVT_BUTTON( ID_EFFECT_PREVIEW, ScienFilterDialog::OnPreview )
505 EVT_BUTTON( wxID_OK, ScienFilterDialog::OnOk )
506 EVT_BUTTON( wxID_CANCEL, ScienFilterDialog::OnCancel )
507 END_EVENT_TABLE()
508
509 ScienFilterDialog::ScienFilterDialog(EffectScienFilter * effect,
510 double loFreq, double hiFreq,
511 wxWindow *parent, wxWindowID id,
512 const wxString &title,
513 const wxPoint &position,
514 const wxSize& size,
515 long style):
516 wxDialog( parent, id, title, position, size, style | wxRESIZE_BORDER | wxMAXIMIZE_BOX )
517 {
518 m_pEffect = effect;
519
520 dBMin = -30.;
521 dBMax = 30;
522
523 #if wxUSE_TOOLTIPS
524 wxToolTip::Enable(true);
525 #endif
526
527 mLoFreq = loFreq;
528 mNyquist = hiFreq;
529
530 memset (effect->mpBiquad, 0, sizeof(effect->mpBiquad));
531 for (int i = 0; i < MAX_FILTER_ORDER/2; i++)
532 {
533 effect->mpBiquad[i] = (BiquadStruct*)calloc (sizeof (BiquadStruct), 1);
534 effect->mpBiquad[i]->fNumerCoeffs [0] = 1.0; // straight-through
535 }
536
537 // Create the dialog
538 MakeScienFilterDialog();
539 }
540
541 ScienFilterDialog::~ScienFilterDialog()
542 {
543 }
544
545 //
546 // Create the ScienFilter dialog
547 //
548 void ScienFilterDialog::MakeScienFilterDialog()
549 {
550 wxStaticText *st;
551 wxSizerFlags flagslabel;
552 wxSizerFlags flagsunits;
553
554 mCutoffCtl = NULL;
555 mRippleCtl = NULL;
556 mStopbandRippleCtl = NULL;
557
558 // TODO: This code would be more readable if using ShuttleGUI.
559 // Some updates to ShuttleGui would help this.
560
561 // Create the base sizer
562 szrV = new wxBoxSizer( wxVERTICAL );
563
564 // -------------------------------------------------------------------
565 // ROW 1: Freq response panel and sliders for vertical scale
566 // -------------------------------------------------------------------
567 szr1 = new wxFlexGridSizer( 3, 0, 0 );
568 szr1->AddGrowableCol( 2, 1 );
569 szr1->AddGrowableRow( 0, 1 );
570 szr1->SetFlexibleDirection( wxBOTH );
571
572 szr2 = new wxBoxSizer( wxVERTICAL );
573 dBMaxSlider = new wxSlider(this, ID_DBMAX, 10, 0, 20,
574 wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE);
575 szr2->Add( dBMaxSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 );
576 dBMinSlider = new wxSlider(this, ID_DBMIN, -10, -120, -10,
577 wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE);
578 szr2->Add( dBMinSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 );
579 szr1->Add( szr2, 0, wxEXPAND|wxALIGN_CENTRE|wxALL, 4 );
580
581 #if wxUSE_ACCESSIBILITY
582 dBMaxSlider->SetName(_("Max dB"));
583 dBMaxSlider->SetAccessible(new SliderAx(dBMaxSlider, wxString(wxT("%d ")) + _("dB")));
584 dBMinSlider->SetName(_("Min dB"));
585 dBMinSlider->SetAccessible(new SliderAx(dBMinSlider, wxString(wxT("%d ")) + _("dB")));
586 #endif
587
588 dBRuler = new RulerPanel(this, wxID_ANY);
589 dBRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes
590 dBRuler->ruler.SetOrientation(wxVERTICAL);
591 dBRuler->ruler.SetRange(30.0, -120.0);
592 dBRuler->ruler.SetFormat(Ruler::LinearDBFormat);
593 dBRuler->ruler.SetUnits(_("dB"));
594 dBRuler->ruler.SetLabelEdges(true);
595 int w, h;
596 dBRuler->ruler.GetMaxSize(&w, NULL);
597 dBRuler->SetSize(wxSize(w, 150)); // height needed for wxGTK
598
599 szr4 = new wxBoxSizer( wxVERTICAL );
600 szr4->AddSpacer(2); // vertical space for panel border and thickness of line
601 szr4->Add( dBRuler, 1, wxEXPAND|wxALIGN_LEFT|wxALL );
602 szr4->AddSpacer(1); // vertical space for thickness of line
603 szr1->Add( szr4, 0, wxEXPAND|wxALIGN_LEFT|wxALL );
604
605 wxSize size;
606 size.Set (400, 200);
607 mPanel = new ScienFilterPanel( mLoFreq, mNyquist,
608 this,
609 ID_FILTERPANEL, wxDefaultPosition, size);
610 szr1->Add( mPanel, 1, wxEXPAND|wxALIGN_CENTRE|wxRIGHT, 4);
611
612 /// Next row of wxFlexGridSizer
613 szr1->Add(1, 1); // horizontal spacer
614 szr1->Add(1, 1); // horizontal spacer
615
616 freqRuler = new RulerPanel(this, wxID_ANY);
617 freqRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes
618 freqRuler->ruler.SetOrientation(wxHORIZONTAL);
619 freqRuler->ruler.SetLog(true);
620 freqRuler->ruler.SetRange(mLoFreq, mNyquist);
621 freqRuler->ruler.SetFormat(Ruler::IntFormat);
622 freqRuler->ruler.SetUnits(wxT(""));
623 freqRuler->ruler.SetFlip(true);
624 freqRuler->ruler.SetLabelEdges(true);
625 freqRuler->ruler.GetMaxSize(NULL, &h);
626 freqRuler->SetMinSize(wxSize(-1, h));
627 szr1->Add( freqRuler, 0, wxEXPAND|wxALIGN_LEFT|wxRIGHT, 4 );
628
629 szrV->Add( szr1, 1, wxEXPAND|wxALIGN_CENTER|wxALL, 0 );
630
631 // -------------------------------------------------------------------
632 // ROW 2 and 3: Type, Order, Ripple, Subtype, Cutoff
633 // -------------------------------------------------------------------
634 szr3 = new wxFlexGridSizer (6, 5, 2); // 6 columns, 5px Vertical gap, 2px Horizontal gap
635 flagslabel.Border(wxLEFT, 12).Align(wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL );
636 flagsunits.Align( wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL );
637
638 st = new wxStaticText(this, wxID_ANY, _("&Filter Type:"));
639 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
640 szr3->Add(st, flagslabel );
641 mFilterTypeCtl = new wxChoice (this, ID_FILTER_TYPE);
642 mFilterTypeCtl->SetName(wxStripMenuCodes(st->GetLabel()));
643 /*i18n-hint: Butterworth is the name of the person after whom the filter type is named.*/
644 mFilterTypeCtl->Append (_("Butterworth"));
645 /*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/
646 mFilterTypeCtl->Append (_("Chebyshev Type I"));
647 /*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/
648 mFilterTypeCtl->Append (_("Chebyshev Type II"));
649 szr3->Add(mFilterTypeCtl);
650
651 /*i18n-hint: 'Order' means the complexity of the filter, and is a number between 1 and 10.*/
652 st = new wxStaticText(this, wxID_ANY, _("O&rder:"));
653 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
654 szr3->Add(st, flagslabel );
655 mFilterOrderCtl = new wxChoice (this, ID_FILTER_ORDER);
656 mFilterOrderCtl->SetName(wxStripMenuCodes(st->GetLabel()));
657 mFilterOrderCtl->Append (wxT("1"));
658 mFilterOrderCtl->Append (wxT("2"));
659 mFilterOrderCtl->Append (wxT("3"));
660 mFilterOrderCtl->Append (wxT("4"));
661 mFilterOrderCtl->Append (wxT("5"));
662 mFilterOrderCtl->Append (wxT("6"));
663 mFilterOrderCtl->Append (wxT("7"));
664 mFilterOrderCtl->Append (wxT("8"));
665 mFilterOrderCtl->Append (wxT("9"));
666 mFilterOrderCtl->Append (wxT("10"));
667 szr3->Add(mFilterOrderCtl);
668
669 st = new wxStaticText(this, wxID_ANY, wxT(""));
670 st->SetName(wxT("")); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
671 szr3->Add(st); // empty field in grid to balance Hz in next row
672
673 szrPass = new wxBoxSizer( wxHORIZONTAL );
674 st = new wxStaticText(this, wxID_ANY, _("&Passband Ripple:"));
675 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
676 szrPass->Add(st, flagslabel);
677 wxSize Size(wxDefaultSize);
678 Size.SetWidth (40);
679 mRippleCtl = new wxTextCtrl (this, ID_RIPPLE, wxT("0.0"), wxDefaultPosition, Size);
680 mRippleCtl->SetName( _("Maximum passband attenuation (dB):"));
681 szrPass->Add(mRippleCtl, 0 );
682 st = new wxStaticText(this, wxID_ANY, _("dB"));
683 st->SetName(st->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
684 szrPass->Add(st, flagsunits);
685 szr3->Add(szrPass);
686
687 st = new wxStaticText(this, wxID_ANY, _("&Subtype:"));
688 szr3->Add(st, flagslabel);
689 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
690 mFilterSubTypeCtl = new wxChoice (this, ID_FILTER_SUBTYPE);
691 mFilterSubTypeCtl->SetName(wxStripMenuCodes(st->GetLabel()));
692 mFilterSubTypeCtl->Append (_("Lowpass"));
693 mFilterSubTypeCtl->Append (_("Highpass"));
694 szr3->Add(mFilterSubTypeCtl);
695
696 st = new wxStaticText(this, wxID_ANY, _("C&utoff:"));
697 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
698 szr3->Add(st, flagslabel);
699 Size.SetWidth (50);
700 mCutoffCtl = new wxTextCtrl (this, ID_CUTOFF, wxT("0.0"), wxDefaultPosition, Size);
701 mCutoffCtl->SetName(_("Cutoff(Hz):"));
702 szr3->Add(mCutoffCtl, 0);
703 st = new wxStaticText(this, wxID_ANY, _("Hz"));
704 st->SetName(st->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
705 szr3->Add(st, flagsunits);
706
707 szrStop = new wxBoxSizer( wxHORIZONTAL );
708 st = new wxStaticText(this, wxID_ANY, _("Minimum S&topband Attenuation:") );
709 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
710 szrStop->Add( st, flagslabel );
711 Size.SetWidth (40);
712 mStopbandRippleCtl = new wxTextCtrl (this, ID_STOPBAND_RIPPLE, wxT("0.0"), wxDefaultPosition, Size);
713 mStopbandRippleCtl->SetName(_("Minimum stopband attenuation (dB):"));
714 szrStop->Add(mStopbandRippleCtl, 0 );
715 st = new wxStaticText(this, wxID_ANY, _("dB"));
716 st->SetName(st->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
717 szrStop->Add(st, flagsunits);
718 szr3->Add(szrStop);
719
720 // Calculate the min size with both pass and stop-band attenuations showing, to stop them jumping around
721 szrPass->Show(true);
722 szrStop->Show(true);
723 szr3->SetMinSize(szr3->CalcMin());
724
725 // -------------------------------------------------------------------
726 // ROW 4: Subtype, Cutoff
727 // -------------------------------------------------------------------
728
729 szrV->Add( szr3, 0, wxALIGN_CENTER | wxALL, 4 );
730
731 // -------------------------------------------------------------------
732 // ROW 5: Preview, OK, & Cancel buttons
733 // -------------------------------------------------------------------
734 szrV->Add(CreateStdButtonSizer(this, ePreviewButton|eCancelButton|eOkButton), 0, wxEXPAND);
735
736 // -------------------------------------------------------------------
737 // Display now
738 // -------------------------------------------------------------------
739 SetAutoLayout(false);
740
741 SetSizerAndFit( szrV );
742 SetSizeHints(GetSize());
743
744 return;
745 }
746
747
748 //
749 // Validate data
750 //
751 bool ScienFilterDialog::Validate()
752 {
753 // In this case I don't think there's anything the user could have screwed up
754 return true;
755 }
756
757 //
758 // Populate the window with relevant variables
759 //
760 bool ScienFilterDialog::TransferDataToWindow()
761 {
762 dBMinSlider->SetValue((int)dBMin);
763 dBMin = 0; // force refresh in TransferGraphLimitsFromWindow()
764
765 dBMaxSlider->SetValue((int)dBMax);
766 dBMax = 0; // force refresh in TransferGraphLimitsFromWindow()
767
768 mFilterTypeCtl->SetSelection (FilterType);
769 mFilterOrderCtl->SetSelection (Order - 1);
770 mFilterSubTypeCtl->SetSelection (FilterSubtype);
771 mCutoffCtl->SetValue (Internat::ToDisplayString(Cutoff));
772 mRippleCtl->SetValue (Internat::ToDisplayString(Ripple));
773 mStopbandRippleCtl->SetValue (Internat::ToDisplayString(StopbandRipple));
774 EnableDisableRippleCtl (FilterType);
775
776 return TransferGraphLimitsFromWindow();
777 }
778
779 //
780 // Retrieve data from the window
781 //
782 bool ScienFilterDialog::TransferGraphLimitsFromWindow()
783 {
784 // Read the sliders and send to the panel
785 wxString tip;
786
787 bool rr = false;
788 int dB = dBMinSlider->GetValue();
789 if (dB != dBMin) {
790 rr = true;
791 dBMin = dB;
792 mPanel->dBMin = dBMin;
793 #if wxUSE_TOOLTIPS
794 tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMin);
795 dBMinSlider->SetToolTip(tip);
796 #endif
797 }
798
799 dB = dBMaxSlider->GetValue();
800 if (dB != dBMax) {
801 rr = true;
802 dBMax = dB;
803 mPanel->dBMax = dBMax;
804 #if wxUSE_TOOLTIPS
805 tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMax);
806 dBMaxSlider->SetToolTip(tip);
807 #endif
808 }
809
810 // Refresh ruler if values have changed
811 if (rr) {
812 int w1, w2, h;
813 dBRuler->ruler.GetMaxSize(&w1, &h);
814 dBRuler->ruler.SetRange(dBMax, dBMin);
815 dBRuler->ruler.GetMaxSize(&w2, &h);
816 if( w1 != w2 ) // Reduces flicker
817 {
818 dBRuler->SetSize(wxSize(w2,h));
819 szr1->Layout();
820 freqRuler->Refresh(false);
821 }
822 dBRuler->Refresh(false);
823 }
824
825 mPanel->Refresh(false);
826
827 return true;
828 }
829
830 bool ScienFilterDialog::CalcFilter (EffectScienFilter* effect)
831 {
832 TrackListOfKindIterator iter(Track::Wave, effect->mTracks);
833 WaveTrack *t = (WaveTrack *) iter.First();
834 float hiFreq;
835 if (t)
836 hiFreq = ((float)(t->GetRate())/2.);
837 else
838 hiFreq = ((float)(GetActiveProject()->GetRate())/2.);
839
840 // Set up the coefficients in all the biquads
841 float fNorm = Cutoff / hiFreq;
842 if (fNorm >= 0.9999)
843 fNorm = 0.9999F;
844 float fC = tan (PI * fNorm / 2);
845 float fDCPoleDistSqr = 1.0F;
846 float fZPoleX, fZPoleY;
847 float fZZeroX, fZZeroY;
848 float beta = cos (fNorm*PI);
849 switch (FilterType)
850 {
851 case 0: // Butterworth
852 if ((Order & 1) == 0)
853 {
854 // Even order
855 for (int iPair = 0; iPair < Order/2; iPair++)
856 {
857 float fSPoleX = fC * cos (PI - (iPair + 0.5) * PI / Order);
858 float fSPoleY = fC * sin (PI - (iPair + 0.5) * PI / Order);
859 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
860 effect->mpBiquad[iPair]->fNumerCoeffs [0] = 1;
861 if (FilterSubtype == 0) // LOWPASS
862 effect->mpBiquad[iPair]->fNumerCoeffs [1] = 2;
863 else
864 effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2;
865 effect->mpBiquad[iPair]->fNumerCoeffs [2] = 1;
866 effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX;
867 effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY);
868 if (FilterSubtype == 0) // LOWPASS
869 fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY);
870 else
871 fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist
872 }
873 }
874 else
875 {
876 // Odd order - first do the 1st-order section
877 float fSPoleX = -fC;
878 float fSPoleY = 0;
879 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
880 effect->mpBiquad[0]->fNumerCoeffs [0] = 1;
881 if (FilterSubtype == 0) // LOWPASS
882 effect->mpBiquad[0]->fNumerCoeffs [1] = 1;
883 else
884 effect->mpBiquad[0]->fNumerCoeffs [1] = -1;
885 effect->mpBiquad[0]->fNumerCoeffs [2] = 0;
886 effect->mpBiquad[0]->fDenomCoeffs [0] = -fZPoleX;
887 effect->mpBiquad[0]->fDenomCoeffs [1] = 0;
888 if (FilterSubtype == 0) // LOWPASS
889 fDCPoleDistSqr = 1 - fZPoleX;
890 else
891 fDCPoleDistSqr = fZPoleX + 1; // dist from Nyquist
892 for (int iPair = 1; iPair <= Order/2; iPair++)
893 {
894 float fSPoleX = fC * cos (PI - iPair * PI / Order);
895 float fSPoleY = fC * sin (PI - iPair * PI / Order);
896 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
897 effect->mpBiquad[iPair]->fNumerCoeffs [0] = 1;
898 if (FilterSubtype == 0) // LOWPASS
899 effect->mpBiquad[iPair]->fNumerCoeffs [1] = 2;
900 else
901 effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2;
902 effect->mpBiquad[iPair]->fNumerCoeffs [2] = 1;
903 effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX;
904 effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY);
905 if (FilterSubtype == 0) // LOWPASS
906 fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY);
907 else
908 fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist
909 }
910 }
911 effect->mpBiquad[0]->fNumerCoeffs [0] *= fDCPoleDistSqr / (1 << Order); // mult by DC dist from poles, divide by dist from zeroes
912 effect->mpBiquad[0]->fNumerCoeffs [1] *= fDCPoleDistSqr / (1 << Order);
913 effect->mpBiquad[0]->fNumerCoeffs [2] *= fDCPoleDistSqr / (1 << Order);
914 break;
915
916 case 1: // Chebyshev Type 1
917 double eps; eps = sqrt (pow (10.0, __max(0.001, Ripple) / 10.0) - 1);
918 double a; a = log (1 / eps + sqrt(1 / square(eps) + 1)) / Order;
919 // Assume even order to start
920 for (int iPair = 0; iPair < Order/2; iPair++)
921 {
922 float fSPoleX = -fC * sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order));
923 float fSPoleY = fC * cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order));
924 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
925 if (FilterSubtype == 0) // LOWPASS
926 {
927 fZZeroX = -1;
928 fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY);
929 fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist
930 }
931 else
932 {
933 // Highpass - do the digital LP->HP transform on the poles and zeroes
934 ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY);
935 fZZeroX = 1;
936 fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist
937 fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist
938 }
939 effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr;
940 effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr;
941 effect->mpBiquad[iPair]->fNumerCoeffs [2] = fDCPoleDistSqr;
942 effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX;
943 effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY);
944 }
945 if ((Order & 1) == 0)
946 {
947 float fTemp = pow (10.0, -__max(0.001, Ripple) / 20.0); // at DC the response is down R dB (for even-order)
948 effect->mpBiquad[0]->fNumerCoeffs [0] *= fTemp;
949 effect->mpBiquad[0]->fNumerCoeffs [1] *= fTemp;
950 effect->mpBiquad[0]->fNumerCoeffs [2] *= fTemp;
951 }
952 else
953 {
954 // Odd order - now do the 1st-order section
955 float fSPoleX = -fC * sinh (a);
956 float fSPoleY = 0;
957 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
958 if (FilterSubtype == 0) // LOWPASS
959 {
960 fZZeroX = -1;
961 fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY));
962 fDCPoleDistSqr /= 2; // dist from zero at Nyquist
963 }
964 else
965 {
966 // Highpass - do the digital LP->HP transform on the poles and zeroes
967 ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY);
968 fZZeroX = 1;
969 fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist
970 fDCPoleDistSqr /= 2; // dist from zero at Nyquist
971 }
972 effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [0] = fDCPoleDistSqr;
973 effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr;
974 effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [2] = 0;
975 effect->mpBiquad[(Order-1)/2]->fDenomCoeffs [0] = -fZPoleX;
976 effect->mpBiquad[(Order-1)/2]->fDenomCoeffs [1] = 0;
977 }
978 break;
979
980 case 2: // Chebyshev Type 2
981 float fSZeroX, fSZeroY;
982 float fSPoleX, fSPoleY;
983 eps = pow (10.0, -__max(0.001, StopbandRipple) / 20.0);
984 a = log (1 / eps + sqrt(1 / square(eps) + 1)) / Order;
985
986 // Assume even order
987 for (int iPair = 0; iPair < Order/2; iPair++)
988 {
989 ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order)),
990 cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order)),
991 &fSPoleX, &fSPoleY);
992 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
993 fSZeroX = 0;
994 fSZeroY = fC / cos (((2 * iPair) + 1) * PI / (2 * Order));
995 BilinTransform (fSZeroX, fSZeroY, &fZZeroX, &fZZeroY);
996
997 if (FilterSubtype == 0) // LOWPASS
998 {
999 fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY);
1000 fDCPoleDistSqr /= Calc2D_DistSqr (1, 0, fZZeroX, fZZeroY);
1001 }
1002 else
1003 {
1004 // Highpass - do the digital LP->HP transform on the poles and zeroes
1005 ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY);
1006 ComplexDiv (beta - fZZeroX, -fZZeroY, 1 - beta * fZZeroX, -beta * fZZeroY, &fZZeroX, &fZZeroY);
1007 fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist
1008 fDCPoleDistSqr /= Calc2D_DistSqr (-1, 0, fZZeroX, fZZeroY);
1009 }
1010 effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr;
1011 effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr;
1012 effect->mpBiquad[iPair]->fNumerCoeffs [2] = (square(fZZeroX) + square(fZZeroY)) * fDCPoleDistSqr;
1013 effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX;
1014 effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY);
1015 }
1016 // Now, if it's odd order, we have one more to do
1017 if (Order & 1)
1018 {
1019 int iPair = (Order-1)/2; // we'll do it as a biquad, but it's just first-order
1020 ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order)),
1021 cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order)),
1022 &fSPoleX, &fSPoleY);
1023 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
1024 fZZeroX = -1; // in the s-plane, the zero is at infinity
1025 fZZeroY = 0;
1026 if (FilterSubtype == 0) // LOWPASS
1027 {
1028 fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY));
1029 fDCPoleDistSqr /= 2;
1030 }
1031 else
1032 {
1033 // Highpass - do the digital LP->HP transform on the poles and zeroes
1034 ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -fZPoleY, &fZPoleX, &fZPoleY);
1035 fZZeroX = 1;
1036 fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist
1037 fDCPoleDistSqr /= 2;
1038 }
1039 effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr;
1040 effect->mpBiquad[iPair]->fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr;
1041 effect->mpBiquad[iPair]->fNumerCoeffs [2] = 0;
1042 effect->mpBiquad[iPair]->fDenomCoeffs [0] = -fZPoleX;
1043 effect->mpBiquad[iPair]->fDenomCoeffs [1] = 0;
1044 }
1045 break;
1046 }
1047 effect->mOrder = Order; // ?? needed for ProcessOne to work in Preview. This probably should be done a different way, but how?
1048 return true;
1049 }
1050
1051 static double s_fChebyCoeffs [MAX_FILTER_ORDER][MAX_FILTER_ORDER+1] = {
1052 // For Chebyshev polynomials of the first kind (see http://en.wikipedia.org/wiki/Chebyshev_polynomial)
1053 // Coeffs are in the order 0, 1, 2...9
1054 {0, 1}, // order 1
1055 {-1, 0, 2}, // order 2 etc.
1056 {0, -3, 0, 4},
1057 {1, 0, -8, 0, 8},
1058 {0, 5, 0, -20, 0, 16},
1059 {-1, 0, 18, 0, -48, 0, 32},
1060 {0, -7, 0, 56, 0, -112, 0, 64},
1061 {1, 0, -32, 0, 160, 0, -256, 0, 128},
1062 {0, 9, 0, -120, 0, 432, 0, -576, 0, 256},
1063 {-1, 0, 50, 0, -400, 0, 1120, 0, -1280, 0, 512}
1064 };
1065
1066 static double ChebyPoly (int Order, double NormFreq) // NormFreq = 1 at the f0 point (where response is R dB down)
1067 {
1068 // Calc cosh (Order * acosh (NormFreq));
1069 double x = 1;
1070 double fSum = 0;
1071 wxASSERT (Order > 0 && Order <= MAX_FILTER_ORDER);
1072 for (int i = 0; i <= Order; i++)
1073 {
1074 fSum += s_fChebyCoeffs [Order-1][i] * x;
1075 x *= NormFreq;
1076 }
1077 return fSum;
1078 }
1079
1080 float ScienFilterDialog::FilterMagnAtFreq (float Freq)
1081 {
1082 float Magn;
1083 if (Freq >= mNyquist)
1084 Freq = mNyquist - 1; // prevent tan(PI/2)
1085 float FreqWarped = tan (PI * Freq/(2*mNyquist));
1086 if (Cutoff >= mNyquist)
1087 Cutoff = mNyquist - 1;
1088 float CutoffWarped = tan (PI * Cutoff/(2*mNyquist));
1089 float fOverflowThresh = pow (10.0, 12.0 / (2*Order)); // once we exceed 10^12 there's not much to be gained and overflow could happen
1090
1091 switch (FilterType)
1092 {
1093 case 0: // Butterworth
1094 default:
1095 switch (FilterSubtype)
1096 {
1097 case 0: // lowpass
1098 default:
1099 if (FreqWarped/CutoffWarped > fOverflowThresh) // prevent pow() overflow
1100 Magn = 0;
1101 else
1102 Magn = sqrt (1 / (1 + pow (FreqWarped/CutoffWarped, 2*Order)));
1103 break;
1104 case 1: // highpass
1105 if (FreqWarped/CutoffWarped > fOverflowThresh)
1106 Magn = 1;
1107 else
1108 Magn = sqrt (pow (FreqWarped/CutoffWarped, 2*Order) / (1 + pow (FreqWarped/CutoffWarped, 2*Order)));
1109 break;
1110 }
1111 break;
1112
1113 case 1: // Chebyshev Type 1
1114 double eps; eps = sqrt(pow (10.0, __max(0.001, Ripple)/10.0) - 1);
1115 switch (FilterSubtype)
1116 {
1117 case 0: // lowpass
1118 default:
1119 Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(Order, FreqWarped/CutoffWarped))));
1120 break;
1121 case 1:
1122 Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(Order, CutoffWarped/FreqWarped))));
1123 break;
1124 }
1125 break;
1126
1127 case 2: // Chebyshev Type 2
1128 eps = 1 / sqrt(pow (10.0, __max(0.001, StopbandRipple)/10.0) - 1);
1129 switch (FilterSubtype)
1130 {
1131 case 0: // lowpass
1132 default:
1133 Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(Order, CutoffWarped/FreqWarped)))));
1134 break;
1135 case 1:
1136 Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(Order, FreqWarped/CutoffWarped)))));
1137 break;
1138 }
1139 break;
1140 }
1141
1142 return Magn;
1143 }
1144
1145
1146
1147 // WDR: handler implementations for ScienFilterDialog
1148
1149 void ScienFilterDialog::OnOrder(wxCommandEvent &WXUNUSED(event))
1150 {
1151 Order = mFilterOrderCtl->GetSelection() + 1; // 0..n-1 -> 1..n
1152 mPanel->Refresh (false);
1153 }
1154
1155 void ScienFilterDialog::OnFilterType (wxCommandEvent &WXUNUSED(event))
1156 {
1157 FilterType = mFilterTypeCtl->GetSelection();
1158 EnableDisableRippleCtl (FilterType);
1159 mPanel->Refresh (false);
1160 }
1161
1162 void ScienFilterDialog::OnFilterSubtype (wxCommandEvent &WXUNUSED(event))
1163 {
1164 FilterSubtype = mFilterSubTypeCtl->GetSelection();
1165 mPanel->Refresh (false);
1166 }
1167
1168 void ScienFilterDialog::OnCutoff (wxCommandEvent &WXUNUSED(event))
1169 {
1170 double CutoffTemp;
1171 if (mCutoffCtl)
1172 {
1173 if (mCutoffCtl->GetValue().ToDouble(&CutoffTemp))
1174 {
1175 Cutoff = CutoffTemp;
1176 if (Cutoff >= mNyquist)
1177 {
1178 Cutoff = mNyquist - 1; // could handle Nyquist as a special case? eg. straight through if LPF
1179 mCutoffCtl->SetValue(Internat::ToDisplayString(Cutoff));
1180 }
1181 wxButton *ok = (wxButton *) FindWindow(wxID_OK);
1182 if (Cutoff < 0.1) // 0.1 Hz min
1183 {
1184 // Disable OK button
1185 ok->Enable(0);
1186 }
1187 else
1188 ok->Enable(1);
1189 }
1190 mPanel->Refresh (false);
1191 }
1192 }
1193
1194 void ScienFilterDialog::OnRipple (wxCommandEvent &WXUNUSED(event))
1195 {
1196 double RippleTemp;
1197 if (mRippleCtl)
1198 {
1199 if (mRippleCtl->GetValue().ToDouble(&RippleTemp))
1200 Ripple = RippleTemp;
1201 mPanel->Refresh (false);
1202 }
1203 }
1204
1205 void ScienFilterDialog::OnStopbandRipple (wxCommandEvent &WXUNUSED(event))
1206 {
1207 double RippleTemp;
1208 if (mStopbandRippleCtl)
1209 {
1210 if (mStopbandRippleCtl->GetValue().ToDouble(&RippleTemp))
1211 StopbandRipple = RippleTemp;
1212 mPanel->Refresh (false);
1213 }
1214 }
1215
1216 void ScienFilterDialog::OnSliderDBMIN(wxCommandEvent &WXUNUSED(event))
1217 {
1218 TransferGraphLimitsFromWindow();
1219 }
1220
1221 void ScienFilterDialog::OnSliderDBMAX(wxCommandEvent &WXUNUSED(event))
1222 {
1223 TransferGraphLimitsFromWindow();
1224 }
1225
1226
1227 void ScienFilterDialog::OnErase(wxEraseEvent &WXUNUSED(event))
1228 {
1229 // Ignore it
1230 }
1231
1232 void ScienFilterDialog::OnPaint(wxPaintEvent &WXUNUSED(event))
1233 {
1234 wxPaintDC dc(this);
1235
1236 #if defined(__WXGTK__)
1237 dc.SetBackground(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)));
1238 #endif
1239
1240 dc.Clear();
1241 }
1242
1243 void ScienFilterDialog::OnSize(wxSizeEvent &event)
1244 {
1245 Layout();
1246
1247 event.Skip();
1248 }
1249
1250 void ScienFilterDialog::OnPreview(wxCommandEvent &WXUNUSED(event))
1251 {
1252 CalcFilter (m_pEffect);
1253 m_pEffect->Preview();
1254 }
1255
1256
1257 void ScienFilterDialog::Finish(bool ok)
1258 {
1259 mPanel = NULL;
1260 EndModal(ok);
1261 }
1262
1263 void ScienFilterDialog::OnCancel(wxCommandEvent &WXUNUSED(event))
1264 {
1265 Finish(false);
1266 }
1267
1268 void ScienFilterDialog::OnOk(wxCommandEvent &event)
1269 {
1270 CalcFilter (m_pEffect);
1271
1272 if( Validate() )
1273 {
1274 Finish(true);
1275 }
1276 else
1277 {
1278 event.Skip(false);
1279 }
1280 }
1281
1282 void ScienFilterDialog::EnableDisableRippleCtl (int FilterType)
1283 {
1284 if (FilterType == 0) // Butterworth
1285 {
1286 szrPass->Show(false);
1287 szrStop->Show(false);
1288 }
1289 else if (FilterType == 1) // Chebyshev Type1
1290 {
1291 szrPass->Show(true);
1292 szrStop->Show(false);
1293 }
1294 else // Chebyshev Type2
1295 {
1296 szrPass->Show(false);
1297 szrStop->Show(true);
1298 }
1299 wxSizeEvent dummy;
1300 OnSize(dummy);
1301 }
1302
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 Effect/ScienFilter.cpp
5
6 Norm C
7 Mitch Golden
8 Vaughan Johnson (Preview)
9
10 *******************************************************************//**
11
12 \file ScienFilter.cpp
13 \brief Implements EffectScienFilter, ScienFilterDialog,
14 ScienFilterPanel.
15
16 *//****************************************************************//**
17
18 \class EffectScienFilter
19 \brief An Effect.
20
21 Performs IIR filtering that emulates analog filters, specifically
22 Butterworth, Chebyshev Type I and Type II. Highpass and lowpass filters
23 are supported, as are filter orders from 1 to 10.
24
25 The filter is applied using biquads
26
27 *//****************************************************************//**
28
29 \class ScienFilterDialog
30 \brief Dialog used with EffectScienFilter
31
32 *//****************************************************************//**
33
34 \class ScienFilterPanel
35 \brief ScienFilterPanel is used with ScienFilterDialog and controls
36 a graph for EffectScienFilter.
37
38 *//*******************************************************************/
39
40 #include "../Audacity.h"
41 #include "ScienFilter.h"
42 #include "Equalization.h" // For SliderAx
43 #include "../AColor.h"
44 #include "../ShuttleGui.h"
45 #include "../PlatformCompatibility.h"
46 #include "../Prefs.h"
47 #include "../Project.h"
48 #include "../WaveTrack.h"
49 #include "../widgets/Ruler.h"
50 #include "../Theme.h"
51 #include "../AllThemeResources.h"
52 #include "../WaveTrack.h"
53 #include "float_cast.h"
54
55 #include <wx/bitmap.h>
56 #include <wx/msgdlg.h>
57 #include <wx/brush.h>
58 #include <wx/dcmemory.h>
59 #include <wx/event.h>
60 #include <wx/image.h>
61 #include <wx/intl.h>
62 #include <wx/stattext.h>
63 #include <wx/string.h>
64 #include <wx/textdlg.h>
65 #include <wx/stdpaths.h>
66 #include <wx/settings.h>
67
68 #if wxUSE_TOOLTIPS
69 #include <wx/tooltip.h>
70 #endif
71 #include <wx/utils.h>
72
73 #include <math.h>
74
75 #include <wx/arrimpl.cpp>
76
77 #define PI 3.1415926535
78 #define square(a) ((a)*(a))
79
80 #ifndef __min
81 #define __min(a,b) ((a) < (b) ? (a) : (b))
82 #endif
83 #ifndef __max
84 #define __max(a,b) ((a) > (b) ? (a) : (b))
85 #endif
86
87 // Local functions
88
89 void EffectScienFilter::ReadPrefs()
90 {
91 double dTemp;
92 gPrefs->Read(wxT("/SciFilter/Order"), &mOrder, 1);
93 mOrder = __max (1, mOrder);
94 mOrder = __min (MAX_FILTER_ORDER, mOrder);
95 gPrefs->Read(wxT("/SciFilter/FilterType"), &mFilterType, 0);
96 mFilterType = __max (0, mFilterType);
97 mFilterType = __min (2, mFilterType);
98 gPrefs->Read(wxT("/SciFilter/FilterSubtype"), &mFilterSubtype, 0);
99 mFilterSubtype = __max (0, mFilterSubtype);
100 mFilterSubtype = __min (1, mFilterSubtype);
101 gPrefs->Read(wxT("/SciFilter/Cutoff"), &dTemp, 1000.0);
102 mCutoff = (float)dTemp;
103 mCutoff = __max (1, mCutoff);
104 mCutoff = __min (100000, mCutoff);
105 gPrefs->Read(wxT("/SciFilter/Ripple"), &dTemp, 1.0);
106 mRipple = dTemp;
107 mRipple = __max (0, mRipple);
108 mRipple = __min (100, mRipple);
109 gPrefs->Read(wxT("/SciFilter/StopbandRipple"), &dTemp, 30.0);
110 mStopbandRipple = dTemp;
111 mStopbandRipple = __max (0, mStopbandRipple);
112 mStopbandRipple = __min (100, mStopbandRipple);
113 }
114
115 EffectScienFilter::EffectScienFilter()
116 {
117 ReadPrefs();
118 mPrompting = false;
119 }
120
121
122 EffectScienFilter::~EffectScienFilter()
123 {
124 }
125
126 bool EffectScienFilter::Init()
127 {
128 int selcount = 0;
129 double rate = 0.0;
130 TrackListIterator iter(GetActiveProject()->GetTracks());
131 Track *t = iter.First();
132 while (t) {
133 if (t->GetSelected() && t->GetKind() == Track::Wave) {
134 WaveTrack *track = (WaveTrack *)t;
135 if (selcount==0) {
136 rate = track->GetRate();
137 }
138 else {
139 if (track->GetRate() != rate) {
140 wxMessageBox(_("To apply a filter, all selected tracks must have the same sample rate."));
141 return(false);
142 }
143 }
144 selcount++;
145 }
146 t = iter.Next();
147 }
148 return(true);}
149
150 bool EffectScienFilter::PromptUser()
151 {
152 // Detect whether we are editing a batch chain by checking the parent window
153 mEditingBatchParams = (mParent != GetActiveProject());
154 if (!mEditingBatchParams)
155 {
156 ReadPrefs();
157 }
158
159 TrackListOfKindIterator iter(Track::Wave, mTracks);
160 WaveTrack *t = (WaveTrack *) iter.First();
161 float hiFreq;
162 if (t)
163 hiFreq = ((float)(t->GetRate())/2.);
164 else
165 hiFreq = ((float)(GetActiveProject()->GetRate())/2.);
166
167 ScienFilterDialog dlog(this, ((double)loFreqI), hiFreq, mParent, -1, _("Classic Filters"));
168
169 dlog.dBMin = mdBMin;
170 dlog.dBMax = mdBMax;
171 dlog.Order = mOrder;
172 dlog.Cutoff = mCutoff;
173 dlog.FilterType = mFilterType;
174 dlog.FilterSubtype = mFilterSubtype;
175 dlog.Ripple = mRipple;
176 dlog.StopbandRipple = mStopbandRipple;
177
178 dlog.CentreOnParent();
179
180 mPrompting = true; // true when previewing, false in batch
181 dlog.ShowModal();
182 mPrompting = false;
183
184 if (!dlog.GetReturnCode())
185 return false;
186
187 mdBMin = dlog.dBMin;
188 mdBMax = dlog.dBMax;
189 mOrder = dlog.Order;
190 mCutoff = dlog.Cutoff;
191 mFilterType = dlog.FilterType;
192 mFilterSubtype = dlog.FilterSubtype;
193 mRipple = dlog.Ripple;
194 mStopbandRipple = dlog.StopbandRipple;
195
196 if (!mEditingBatchParams)
197 {
198 // Save preferences
199 gPrefs->Write(wxT("/SciFilter/Order"), mOrder);
200 gPrefs->Write(wxT("/SciFilter/FilterType"), mFilterType);
201 gPrefs->Write(wxT("/SciFilter/FilterSubtype"), mFilterSubtype);
202 gPrefs->Write(wxT("/SciFilter/Cutoff"), mCutoff);
203 gPrefs->Write(wxT("/SciFilter/Ripple"), mRipple);
204 gPrefs->Write(wxT("/SciFilter/StopbandRipple"), mStopbandRipple);
205 gPrefs->Flush();
206 }
207
208 return true;
209 }
210
211 bool EffectScienFilter::DontPromptUser()
212 {
213 TrackListOfKindIterator iter(Track::Wave, mTracks);
214 WaveTrack *t = (WaveTrack *) iter.First();
215 float hiFreq;
216 if (t)
217 hiFreq = ((float)(t->GetRate())/2.);
218 else
219 hiFreq = ((float)(GetActiveProject()->GetRate())/2.);
220 /*i18n-hint: 'Classic Filters' is an audio effect. It's a low-pass or high-pass
221 filter with specfic characteristics. */
222 ScienFilterDialog dlog(this, ((double)loFreqI), hiFreq, NULL, -1, _("Classic Filters"));
223 dlog.dBMin = mdBMin;
224 dlog.dBMax = mdBMax;
225 dlog.Order = mOrder;
226 dlog.Cutoff = mCutoff;
227 dlog.FilterType = mFilterType;
228 dlog.FilterSubtype = mFilterSubtype;
229 dlog.Ripple = mRipple;
230
231 dlog.CalcFilter(this);
232
233 return true;
234 }
235
236 bool EffectScienFilter::TransferParameters( Shuttle & shuttle )
237 {
238 // if shuttle.mbStoreInClient is true, read prefs ScienFilter/FilterType (etc.) string and put into mFilterType (etc.)
239 // else put mFilterType (etc.) into string form and write prefs
240 shuttle.TransferInt(wxT("FilterType"),mFilterType,0);
241 shuttle.TransferInt(wxT("FilterSubtype"),mFilterSubtype,0); // etc.
242 shuttle.TransferInt(wxT("Order"),mOrder,2);
243 shuttle.TransferFloat(wxT("Cutoff"),mCutoff,1000);
244 shuttle.TransferFloat(wxT("PassbandRipple"),mRipple,1);
245 shuttle.TransferFloat(wxT("StopbandRipple"),mStopbandRipple,30);
246
247 if(!mPrompting)
248 DontPromptUser(); // not previewing, ie batch mode or initial setup
249 return true;
250 }
251
252 bool EffectScienFilter::Process()
253 {
254 this->CopyInputTracks(); // Set up mOutputTracks.
255 bool bGoodResult = true;
256
257 SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks);
258 WaveTrack *track = (WaveTrack *) iter.First();
259 int count = 0;
260 while (track)
261 {
262 double trackStart = track->GetStartTime();
263 double trackEnd = track->GetEndTime();
264 double t0 = mT0 < trackStart? trackStart: mT0;
265 double t1 = mT1 > trackEnd? trackEnd: mT1;
266
267 if (t1 > t0) {
268 sampleCount start = track->TimeToLongSamples(t0);
269 sampleCount end = track->TimeToLongSamples(t1);
270 sampleCount len = (sampleCount)(end - start);
271
272 if (!ProcessOne(count, track, start, len))
273 {
274 bGoodResult = false;
275 break;
276 }
277 }
278
279 track = (WaveTrack *) iter.Next();
280 count++;
281 }
282
283 this->ReplaceProcessedTracks(bGoodResult);
284 return bGoodResult;
285 }
286
287
288 bool EffectScienFilter::ProcessOne(int count, WaveTrack * t,
289 sampleCount start, sampleCount len)
290 {
291 // Create a new WaveTrack to hold all of the output
292 AudacityProject *p = GetActiveProject();
293 WaveTrack *output = p->GetTrackFactory()->NewWaveTrack(floatSample, t->GetRate());
294
295 sampleCount s = start;
296 sampleCount idealBlockLen = t->GetMaxBlockSize();
297 float *buffer = new float[idealBlockLen];
298 sampleCount originalLen = len;
299
300 TrackProgress(count, 0.0);
301 bool bLoopSuccess = true;
302
303 for (int iPair = 0; iPair < (mOrder+1)/2; iPair++)
304 mpBiquad [iPair]->fPrevIn = mpBiquad [iPair]->fPrevPrevIn = mpBiquad [iPair]->fPrevOut = mpBiquad [iPair]->fPrevPrevOut = 0;
305
306 while(len)
307 {
308 sampleCount block = idealBlockLen;
309 if (block > len)
310 block = len;
311
312 t->Get((samplePtr)buffer, floatSample, s, block);
313
314 for (int iPair = 0; iPair < (mOrder+1)/2; iPair++)
315 {
316 mpBiquad[iPair]->pfIn = buffer;
317 mpBiquad[iPair]->pfOut = buffer;
318 Biquad_Process (mpBiquad[iPair], block);
319 }
320 output->Append ((samplePtr)buffer, floatSample, block);
321 len -= block;
322 s += block;
323
324 if (TrackProgress (count, (s-start)/(double)originalLen))
325 {
326 bLoopSuccess = false;
327 break;
328 }
329 }
330 if (bLoopSuccess)
331 {
332 output->Flush();
333 // Now move the appropriate bit of the output back to the track
334 float *bigBuffer = new float[originalLen];
335 output->Get((samplePtr)bigBuffer, floatSample, 0, originalLen);
336 t->Set((samplePtr)bigBuffer, floatSample, start, originalLen);
337 delete[] bigBuffer;
338 }
339
340 delete[] buffer;
341 delete output;
342
343 return bLoopSuccess;
344 }
345
346 void EffectScienFilter::Filter(sampleCount WXUNUSED(len),
347 float *WXUNUSED(buffer))
348 {
349 }
350
351
352 //----------------------------------------------------------------------------
353 // ScienFilterPanel
354 //----------------------------------------------------------------------------
355
356 BEGIN_EVENT_TABLE(ScienFilterPanel, wxPanel)
357 EVT_PAINT(ScienFilterPanel::OnPaint)
358 EVT_SIZE(ScienFilterPanel::OnSize)
359 END_EVENT_TABLE()
360
361 ScienFilterPanel::ScienFilterPanel( double loFreq, double hiFreq,
362 ScienFilterDialog *parent,
363 wxWindowID id, const wxPoint& pos, const wxSize& size):
364 wxPanel(parent, id, pos, size)
365 {
366 mBitmap = NULL;
367 mWidth = 0;
368 mHeight = 0;
369 mLoFreq = loFreq;
370 mHiFreq = hiFreq;
371 mParent = parent;
372 }
373
374 ScienFilterPanel::~ScienFilterPanel()
375 {
376 if (mBitmap)
377 delete mBitmap;
378 }
379
380 void ScienFilterPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
381 {
382 Refresh( false );
383 }
384
385 void ScienFilterPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
386 {
387 wxPaintDC dc(this);
388 int width, height;
389 GetSize(&width, &height);
390
391 if (!mBitmap || mWidth!=width || mHeight!=height)
392 {
393 if (mBitmap)
394 delete mBitmap;
395
396 mWidth = width;
397 mHeight = height;
398 mBitmap = new wxBitmap(mWidth, mHeight);
399 }
400
401 wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
402
403 wxMemoryDC memDC;
404 memDC.SelectObject(*mBitmap);
405
406 wxRect bkgndRect;
407 bkgndRect.x = 0;
408 bkgndRect.y = 0;
409 bkgndRect.width = mWidth;
410 bkgndRect.height = mHeight;
411 memDC.SetBrush(bkgndBrush);
412 memDC.SetPen(*wxTRANSPARENT_PEN);
413 memDC.DrawRectangle(bkgndRect);
414
415 bkgndRect.y = mHeight;
416 memDC.DrawRectangle(bkgndRect);
417
418 wxRect border;
419 border.x = 0;
420 border.y = 0;
421 border.width = mWidth;
422 border.height = mHeight;
423
424 memDC.SetBrush(*wxWHITE_BRUSH);
425 memDC.SetPen(*wxBLACK_PEN);
426 memDC.DrawRectangle(border);
427
428 mEnvRect = border;
429 mEnvRect.Deflate(2, 2);
430
431 // Pure blue x-axis line
432 memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 1, wxSOLID));
433 int center = (int) (mEnvRect.height * dBMax/(dBMax-dBMin) + .5);
434 AColor::Line(memDC,
435 mEnvRect.GetLeft(), mEnvRect.y + center,
436 mEnvRect.GetRight(), mEnvRect.y + center);
437
438 //Now draw the actual response that you will get.
439 //mFilterFunc has a linear scale, window has a log one so we have to fiddle about
440 memDC.SetPen(wxPen(theTheme.Colour( clrResponseLines ), 3, wxSOLID));
441 double scale = (double)mEnvRect.height/(dBMax-dBMin); // pixels per dB
442 double yF; // gain at this freq
443
444 double loLog = log10(mLoFreq);
445 double step = log10(mHiFreq) - loLog;
446 step /= ((double)mEnvRect.width-1.);
447 double freq; // actual freq corresponding to x position
448 int x, y, xlast = 0, ylast = 0;
449 for(int i=0; i<mEnvRect.width; i++)
450 {
451 x = mEnvRect.x + i;
452 freq = pow(10., loLog + i*step); //Hz
453 yF = mParent->FilterMagnAtFreq (freq);
454 yF = 20*log10(yF);
455
456 if(yF < dBMin)
457 yF = dBMin;
458 yF = center-scale*yF;
459 if(yF>mEnvRect.height)
460 yF = mEnvRect.height - 1;
461 if(yF<0.)
462 yF=0.;
463 y = (int)(yF+.5);
464
465 if (i != 0 && (y < mEnvRect.height-1 || ylast < mEnvRect.y + mEnvRect.height-1))
466 {
467 AColor::Line (memDC, xlast, ylast, x, mEnvRect.y + y);
468 }
469 xlast = x;
470 ylast = mEnvRect.y + y;
471 }
472
473 memDC.SetPen(*wxBLACK_PEN);
474 mParent->freqRuler->ruler.DrawGrid(memDC, mEnvRect.height+2, true, true, 0, 1);
475 mParent->dBRuler->ruler.DrawGrid(memDC, mEnvRect.width+2, true, true, 1, 2);
476
477 dc.Blit(0, 0, mWidth, mHeight,
478 &memDC, 0, 0, wxCOPY, FALSE);
479 }
480
481
482 // WDR: class implementations
483
484 //----------------------------------------------------------------------------
485 // ScienFilterDialog
486 //----------------------------------------------------------------------------
487
488 // WDR: event table for ScienFilterDialog
489
490 BEGIN_EVENT_TABLE(ScienFilterDialog,wxDialog)
491 EVT_SIZE( ScienFilterDialog::OnSize )
492 EVT_PAINT( ScienFilterDialog::OnPaint )
493 EVT_ERASE_BACKGROUND( ScienFilterDialog::OnErase )
494
495 EVT_SLIDER( ID_DBMAX, ScienFilterDialog::OnSliderDBMAX )
496 EVT_SLIDER( ID_DBMIN, ScienFilterDialog::OnSliderDBMIN )
497 EVT_CHOICE( ID_FILTER_ORDER, ScienFilterDialog::OnOrder)
498 EVT_CHOICE( ID_FILTER_TYPE, ScienFilterDialog::OnFilterType)
499 EVT_CHOICE( ID_FILTER_SUBTYPE, ScienFilterDialog::OnFilterSubtype)
500 EVT_TEXT( ID_CUTOFF, ScienFilterDialog::OnCutoff)
501 EVT_TEXT( ID_RIPPLE, ScienFilterDialog::OnRipple)
502 EVT_TEXT( ID_STOPBAND_RIPPLE, ScienFilterDialog::OnStopbandRipple)
503
504 EVT_BUTTON( ID_EFFECT_PREVIEW, ScienFilterDialog::OnPreview )
505 EVT_BUTTON( wxID_OK, ScienFilterDialog::OnOk )
506 EVT_BUTTON( wxID_CANCEL, ScienFilterDialog::OnCancel )
507 END_EVENT_TABLE()
508
509 ScienFilterDialog::ScienFilterDialog(EffectScienFilter * effect,
510 double loFreq, double hiFreq,
511 wxWindow *parent, wxWindowID id,
512 const wxString &title,
513 const wxPoint &position,
514 const wxSize& size,
515 long style):
516 wxDialog( parent, id, title, position, size, style | wxRESIZE_BORDER | wxMAXIMIZE_BOX )
517 {
518 m_pEffect = effect;
519
520 dBMin = -30.;
521 dBMax = 30;
522
523 #if wxUSE_TOOLTIPS
524 wxToolTip::Enable(true);
525 #endif
526
527 mLoFreq = loFreq;
528 mNyquist = hiFreq;
529
530 memset (effect->mpBiquad, 0, sizeof(effect->mpBiquad));
531 for (int i = 0; i < MAX_FILTER_ORDER/2; i++)
532 {
533 effect->mpBiquad[i] = (BiquadStruct*)calloc (sizeof (BiquadStruct), 1);
534 effect->mpBiquad[i]->fNumerCoeffs [0] = 1.0; // straight-through
535 }
536
537 // Create the dialog
538 MakeScienFilterDialog();
539 }
540
541 ScienFilterDialog::~ScienFilterDialog()
542 {
543 }
544
545 //
546 // Create the ScienFilter dialog
547 //
548 void ScienFilterDialog::MakeScienFilterDialog()
549 {
550 wxStaticText *st;
551 wxSizerFlags flagslabel;
552 wxSizerFlags flagsunits;
553
554 mCutoffCtl = NULL;
555 mRippleCtl = NULL;
556 mStopbandRippleCtl = NULL;
557
558 // TODO: This code would be more readable if using ShuttleGUI.
559 // Some updates to ShuttleGui would help this.
560
561 // Create the base sizer
562 szrV = new wxBoxSizer( wxVERTICAL );
563
564 // -------------------------------------------------------------------
565 // ROW 1: Freq response panel and sliders for vertical scale
566 // -------------------------------------------------------------------
567 szr1 = new wxFlexGridSizer( 3, 0, 0 );
568 szr1->AddGrowableCol( 2, 1 );
569 szr1->AddGrowableRow( 0, 1 );
570 szr1->SetFlexibleDirection( wxBOTH );
571
572 szr2 = new wxBoxSizer( wxVERTICAL );
573 dBMaxSlider = new wxSlider(this, ID_DBMAX, 10, 0, 20,
574 wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE);
575 szr2->Add( dBMaxSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 );
576 dBMinSlider = new wxSlider(this, ID_DBMIN, -10, -120, -10,
577 wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE);
578 szr2->Add( dBMinSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 );
579 szr1->Add( szr2, 0, wxEXPAND|wxALIGN_CENTRE|wxALL, 4 );
580
581 #if wxUSE_ACCESSIBILITY
582 dBMaxSlider->SetName(_("Max dB"));
583 dBMaxSlider->SetAccessible(new SliderAx(dBMaxSlider, wxString(wxT("%d ")) + _("dB")));
584 dBMinSlider->SetName(_("Min dB"));
585 dBMinSlider->SetAccessible(new SliderAx(dBMinSlider, wxString(wxT("%d ")) + _("dB")));
586 #endif
587
588 dBRuler = new RulerPanel(this, wxID_ANY);
589 dBRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes
590 dBRuler->ruler.SetOrientation(wxVERTICAL);
591 dBRuler->ruler.SetRange(30.0, -120.0);
592 dBRuler->ruler.SetFormat(Ruler::LinearDBFormat);
593 dBRuler->ruler.SetUnits(_("dB"));
594 dBRuler->ruler.SetLabelEdges(true);
595 int w, h;
596 dBRuler->ruler.GetMaxSize(&w, NULL);
597 dBRuler->SetSize(wxSize(w, 150)); // height needed for wxGTK
598
599 szr4 = new wxBoxSizer( wxVERTICAL );
600 szr4->AddSpacer(2); // vertical space for panel border and thickness of line
601 szr4->Add( dBRuler, 1, wxEXPAND|wxALIGN_LEFT|wxALL );
602 szr4->AddSpacer(1); // vertical space for thickness of line
603 szr1->Add( szr4, 0, wxEXPAND|wxALIGN_LEFT|wxALL );
604
605 wxSize size;
606 size.Set (400, 200);
607 mPanel = new ScienFilterPanel( mLoFreq, mNyquist,
608 this,
609 ID_FILTERPANEL, wxDefaultPosition, size);
610 szr1->Add( mPanel, 1, wxEXPAND|wxALIGN_CENTRE|wxRIGHT, 4);
611
612 /// Next row of wxFlexGridSizer
613 szr1->Add(1, 1); // horizontal spacer
614 szr1->Add(1, 1); // horizontal spacer
615
616 freqRuler = new RulerPanel(this, wxID_ANY);
617 freqRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes
618 freqRuler->ruler.SetOrientation(wxHORIZONTAL);
619 freqRuler->ruler.SetLog(true);
620 freqRuler->ruler.SetRange(mLoFreq, mNyquist);
621 freqRuler->ruler.SetFormat(Ruler::IntFormat);
622 freqRuler->ruler.SetUnits(wxT(""));
623 freqRuler->ruler.SetFlip(true);
624 freqRuler->ruler.SetLabelEdges(true);
625 freqRuler->ruler.GetMaxSize(NULL, &h);
626 freqRuler->SetMinSize(wxSize(-1, h));
627 szr1->Add( freqRuler, 0, wxEXPAND|wxALIGN_LEFT|wxRIGHT, 4 );
628
629 szrV->Add( szr1, 1, wxEXPAND|wxALIGN_CENTER|wxALL, 0 );
630
631 // -------------------------------------------------------------------
632 // ROW 2 and 3: Type, Order, Ripple, Subtype, Cutoff
633 // -------------------------------------------------------------------
634 szr3 = new wxFlexGridSizer (6, 5, 2); // 6 columns, 5px Vertical gap, 2px Horizontal gap
635 flagslabel.Border(wxLEFT, 12).Align(wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL );
636 flagsunits.Align( wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL );
637
638 st = new wxStaticText(this, wxID_ANY, _("&Filter Type:"));
639 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
640 szr3->Add(st, flagslabel );
641 mFilterTypeCtl = new wxChoice (this, ID_FILTER_TYPE);
642 mFilterTypeCtl->SetName(wxStripMenuCodes(st->GetLabel()));
643 /*i18n-hint: Butterworth is the name of the person after whom the filter type is named.*/
644 mFilterTypeCtl->Append (_("Butterworth"));
645 /*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/
646 mFilterTypeCtl->Append (_("Chebyshev Type I"));
647 /*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/
648 mFilterTypeCtl->Append (_("Chebyshev Type II"));
649 szr3->Add(mFilterTypeCtl);
650
651 /*i18n-hint: 'Order' means the complexity of the filter, and is a number between 1 and 10.*/
652 st = new wxStaticText(this, wxID_ANY, _("O&rder:"));
653 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
654 szr3->Add(st, flagslabel );
655 mFilterOrderCtl = new wxChoice (this, ID_FILTER_ORDER);
656 mFilterOrderCtl->SetName(wxStripMenuCodes(st->GetLabel()));
657 mFilterOrderCtl->Append (wxT("1"));
658 mFilterOrderCtl->Append (wxT("2"));
659 mFilterOrderCtl->Append (wxT("3"));
660 mFilterOrderCtl->Append (wxT("4"));
661 mFilterOrderCtl->Append (wxT("5"));
662 mFilterOrderCtl->Append (wxT("6"));
663 mFilterOrderCtl->Append (wxT("7"));
664 mFilterOrderCtl->Append (wxT("8"));
665 mFilterOrderCtl->Append (wxT("9"));
666 mFilterOrderCtl->Append (wxT("10"));
667 szr3->Add(mFilterOrderCtl);
668
669 st = new wxStaticText(this, wxID_ANY, wxT(""));
670 st->SetName(wxT("")); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
671 szr3->Add(st); // empty field in grid to balance Hz in next row
672
673 szrPass = new wxBoxSizer( wxHORIZONTAL );
674 st = new wxStaticText(this, wxID_ANY, _("&Passband Ripple:"));
675 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
676 szrPass->Add(st, flagslabel);
677 wxSize Size(wxDefaultSize);
678 Size.SetWidth (40);
679 mRippleCtl = new wxTextCtrl (this, ID_RIPPLE, wxT("0.0"), wxDefaultPosition, Size);
680 mRippleCtl->SetName( _("Maximum passband attenuation (dB):"));
681 szrPass->Add(mRippleCtl, 0 );
682 st = new wxStaticText(this, wxID_ANY, _("dB"));
683 st->SetName(st->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
684 szrPass->Add(st, flagsunits);
685 szr3->Add(szrPass);
686
687 st = new wxStaticText(this, wxID_ANY, _("&Subtype:"));
688 szr3->Add(st, flagslabel);
689 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
690 mFilterSubTypeCtl = new wxChoice (this, ID_FILTER_SUBTYPE);
691 mFilterSubTypeCtl->SetName(wxStripMenuCodes(st->GetLabel()));
692 mFilterSubTypeCtl->Append (_("Lowpass"));
693 mFilterSubTypeCtl->Append (_("Highpass"));
694 szr3->Add(mFilterSubTypeCtl);
695
696 st = new wxStaticText(this, wxID_ANY, _("C&utoff:"));
697 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
698 szr3->Add(st, flagslabel);
699 Size.SetWidth (50);
700 mCutoffCtl = new wxTextCtrl (this, ID_CUTOFF, wxT("0.0"), wxDefaultPosition, Size);
701 mCutoffCtl->SetName(_("Cutoff(Hz):"));
702 szr3->Add(mCutoffCtl, 0);
703 st = new wxStaticText(this, wxID_ANY, _("Hz"));
704 st->SetName(st->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
705 szr3->Add(st, flagsunits);
706
707 szrStop = new wxBoxSizer( wxHORIZONTAL );
708 st = new wxStaticText(this, wxID_ANY, _("Minimum S&topband Attenuation:") );
709 st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
710 szrStop->Add( st, flagslabel );
711 Size.SetWidth (40);
712 mStopbandRippleCtl = new wxTextCtrl (this, ID_STOPBAND_RIPPLE, wxT("0.0"), wxDefaultPosition, Size);
713 mStopbandRippleCtl->SetName(_("Minimum stopband attenuation (dB):"));
714 szrStop->Add(mStopbandRippleCtl, 0 );
715 st = new wxStaticText(this, wxID_ANY, _("dB"));
716 st->SetName(st->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
717 szrStop->Add(st, flagsunits);
718 szr3->Add(szrStop);
719
720 // Calculate the min size with both pass and stop-band attenuations showing, to stop them jumping around
721 szrPass->Show(true);
722 szrStop->Show(true);
723 szr3->SetMinSize(szr3->CalcMin());
724
725 // -------------------------------------------------------------------
726 // ROW 4: Subtype, Cutoff
727 // -------------------------------------------------------------------
728
729 szrV->Add( szr3, 0, wxALIGN_CENTER | wxALL, 4 );
730
731 // -------------------------------------------------------------------
732 // ROW 5: Preview, OK, & Cancel buttons
733 // -------------------------------------------------------------------
734 szrV->Add(CreateStdButtonSizer(this, ePreviewButton|eCancelButton|eOkButton), 0, wxEXPAND);
735
736 // -------------------------------------------------------------------
737 // Display now
738 // -------------------------------------------------------------------
739 SetAutoLayout(false);
740
741 SetSizerAndFit( szrV );
742 SetSizeHints(GetSize());
743
744 return;
745 }
746
747
748 //
749 // Validate data
750 //
751 bool ScienFilterDialog::Validate()
752 {
753 // In this case I don't think there's anything the user could have screwed up
754 return true;
755 }
756
757 //
758 // Populate the window with relevant variables
759 //
760 bool ScienFilterDialog::TransferDataToWindow()
761 {
762 dBMinSlider->SetValue((int)dBMin);
763 dBMin = 0; // force refresh in TransferGraphLimitsFromWindow()
764
765 dBMaxSlider->SetValue((int)dBMax);
766 dBMax = 0; // force refresh in TransferGraphLimitsFromWindow()
767
768 mFilterTypeCtl->SetSelection (FilterType);
769 mFilterOrderCtl->SetSelection (Order - 1);
770 mFilterSubTypeCtl->SetSelection (FilterSubtype);
771 mCutoffCtl->SetValue (Internat::ToDisplayString(Cutoff));
772 mRippleCtl->SetValue (Internat::ToDisplayString(Ripple));
773 mStopbandRippleCtl->SetValue (Internat::ToDisplayString(StopbandRipple));
774 EnableDisableRippleCtl (FilterType);
775
776 return TransferGraphLimitsFromWindow();
777 }
778
779 //
780 // Retrieve data from the window
781 //
782 bool ScienFilterDialog::TransferGraphLimitsFromWindow()
783 {
784 // Read the sliders and send to the panel
785 wxString tip;
786
787 bool rr = false;
788 int dB = dBMinSlider->GetValue();
789 if (dB != dBMin) {
790 rr = true;
791 dBMin = dB;
792 mPanel->dBMin = dBMin;
793 #if wxUSE_TOOLTIPS
794 tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMin);
795 dBMinSlider->SetToolTip(tip);
796 #endif
797 }
798
799 dB = dBMaxSlider->GetValue();
800 if (dB != dBMax) {
801 rr = true;
802 dBMax = dB;
803 mPanel->dBMax = dBMax;
804 #if wxUSE_TOOLTIPS
805 tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMax);
806 dBMaxSlider->SetToolTip(tip);
807 #endif
808 }
809
810 // Refresh ruler if values have changed
811 if (rr) {
812 int w1, w2, h;
813 dBRuler->ruler.GetMaxSize(&w1, &h);
814 dBRuler->ruler.SetRange(dBMax, dBMin);
815 dBRuler->ruler.GetMaxSize(&w2, &h);
816 if( w1 != w2 ) // Reduces flicker
817 {
818 dBRuler->SetSize(wxSize(w2,h));
819 szr1->Layout();
820 freqRuler->Refresh(false);
821 }
822 dBRuler->Refresh(false);
823 }
824
825 mPanel->Refresh(false);
826
827 return true;
828 }
829
830 bool ScienFilterDialog::CalcFilter (EffectScienFilter* effect)
831 {
832 TrackListOfKindIterator iter(Track::Wave, effect->mTracks);
833 WaveTrack *t = (WaveTrack *) iter.First();
834 float hiFreq;
835 if (t)
836 hiFreq = ((float)(t->GetRate())/2.);
837 else
838 hiFreq = ((float)(GetActiveProject()->GetRate())/2.);
839
840 // Set up the coefficients in all the biquads
841 float fNorm = Cutoff / hiFreq;
842 if (fNorm >= 0.9999)
843 fNorm = 0.9999F;
844 float fC = tan (PI * fNorm / 2);
845 float fDCPoleDistSqr = 1.0F;
846 float fZPoleX, fZPoleY;
847 float fZZeroX, fZZeroY;
848 float beta = cos (fNorm*PI);
849 switch (FilterType)
850 {
851 case 0: // Butterworth
852 if ((Order & 1) == 0)
853 {
854 // Even order
855 for (int iPair = 0; iPair < Order/2; iPair++)
856 {
857 float fSPoleX = fC * cos (PI - (iPair + 0.5) * PI / Order);
858 float fSPoleY = fC * sin (PI - (iPair + 0.5) * PI / Order);
859 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
860 effect->mpBiquad[iPair]->fNumerCoeffs [0] = 1;
861 if (FilterSubtype == 0) // LOWPASS
862 effect->mpBiquad[iPair]->fNumerCoeffs [1] = 2;
863 else
864 effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2;
865 effect->mpBiquad[iPair]->fNumerCoeffs [2] = 1;
866 effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX;
867 effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY);
868 if (FilterSubtype == 0) // LOWPASS
869 fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY);
870 else
871 fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist
872 }
873 }
874 else
875 {
876 // Odd order - first do the 1st-order section
877 float fSPoleX = -fC;
878 float fSPoleY = 0;
879 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
880 effect->mpBiquad[0]->fNumerCoeffs [0] = 1;
881 if (FilterSubtype == 0) // LOWPASS
882 effect->mpBiquad[0]->fNumerCoeffs [1] = 1;
883 else
884 effect->mpBiquad[0]->fNumerCoeffs [1] = -1;
885 effect->mpBiquad[0]->fNumerCoeffs [2] = 0;
886 effect->mpBiquad[0]->fDenomCoeffs [0] = -fZPoleX;
887 effect->mpBiquad[0]->fDenomCoeffs [1] = 0;
888 if (FilterSubtype == 0) // LOWPASS
889 fDCPoleDistSqr = 1 - fZPoleX;
890 else
891 fDCPoleDistSqr = fZPoleX + 1; // dist from Nyquist
892 for (int iPair = 1; iPair <= Order/2; iPair++)
893 {
894 float fSPoleX = fC * cos (PI - iPair * PI / Order);
895 float fSPoleY = fC * sin (PI - iPair * PI / Order);
896 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
897 effect->mpBiquad[iPair]->fNumerCoeffs [0] = 1;
898 if (FilterSubtype == 0) // LOWPASS
899 effect->mpBiquad[iPair]->fNumerCoeffs [1] = 2;
900 else
901 effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2;
902 effect->mpBiquad[iPair]->fNumerCoeffs [2] = 1;
903 effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX;
904 effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY);
905 if (FilterSubtype == 0) // LOWPASS
906 fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY);
907 else
908 fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist
909 }
910 }
911 effect->mpBiquad[0]->fNumerCoeffs [0] *= fDCPoleDistSqr / (1 << Order); // mult by DC dist from poles, divide by dist from zeroes
912 effect->mpBiquad[0]->fNumerCoeffs [1] *= fDCPoleDistSqr / (1 << Order);
913 effect->mpBiquad[0]->fNumerCoeffs [2] *= fDCPoleDistSqr / (1 << Order);
914 break;
915
916 case 1: // Chebyshev Type 1
917 double eps; eps = sqrt (pow (10.0, __max(0.001, Ripple) / 10.0) - 1);
918 double a; a = log (1 / eps + sqrt(1 / square(eps) + 1)) / Order;
919 // Assume even order to start
920 for (int iPair = 0; iPair < Order/2; iPair++)
921 {
922 float fSPoleX = -fC * sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order));
923 float fSPoleY = fC * cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order));
924 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
925 if (FilterSubtype == 0) // LOWPASS
926 {
927 fZZeroX = -1;
928 fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY);
929 fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist
930 }
931 else
932 {
933 // Highpass - do the digital LP->HP transform on the poles and zeroes
934 ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY);
935 fZZeroX = 1;
936 fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist
937 fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist
938 }
939 effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr;
940 effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr;
941 effect->mpBiquad[iPair]->fNumerCoeffs [2] = fDCPoleDistSqr;
942 effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX;
943 effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY);
944 }
945 if ((Order & 1) == 0)
946 {
947 float fTemp = pow (10.0, -__max(0.001, Ripple) / 20.0); // at DC the response is down R dB (for even-order)
948 effect->mpBiquad[0]->fNumerCoeffs [0] *= fTemp;
949 effect->mpBiquad[0]->fNumerCoeffs [1] *= fTemp;
950 effect->mpBiquad[0]->fNumerCoeffs [2] *= fTemp;
951 }
952 else
953 {
954 // Odd order - now do the 1st-order section
955 float fSPoleX = -fC * sinh (a);
956 float fSPoleY = 0;
957 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
958 if (FilterSubtype == 0) // LOWPASS
959 {
960 fZZeroX = -1;
961 fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY));
962 fDCPoleDistSqr /= 2; // dist from zero at Nyquist
963 }
964 else
965 {
966 // Highpass - do the digital LP->HP transform on the poles and zeroes
967 ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY);
968 fZZeroX = 1;
969 fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist
970 fDCPoleDistSqr /= 2; // dist from zero at Nyquist
971 }
972 effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [0] = fDCPoleDistSqr;
973 effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr;
974 effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [2] = 0;
975 effect->mpBiquad[(Order-1)/2]->fDenomCoeffs [0] = -fZPoleX;
976 effect->mpBiquad[(Order-1)/2]->fDenomCoeffs [1] = 0;
977 }
978 break;
979
980 case 2: // Chebyshev Type 2
981 float fSZeroX, fSZeroY;
982 float fSPoleX, fSPoleY;
983 eps = pow (10.0, -__max(0.001, StopbandRipple) / 20.0);
984 a = log (1 / eps + sqrt(1 / square(eps) + 1)) / Order;
985
986 // Assume even order
987 for (int iPair = 0; iPair < Order/2; iPair++)
988 {
989 ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order)),
990 cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order)),
991 &fSPoleX, &fSPoleY);
992 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
993 fSZeroX = 0;
994 fSZeroY = fC / cos (((2 * iPair) + 1) * PI / (2 * Order));
995 BilinTransform (fSZeroX, fSZeroY, &fZZeroX, &fZZeroY);
996
997 if (FilterSubtype == 0) // LOWPASS
998 {
999 fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY);
1000 fDCPoleDistSqr /= Calc2D_DistSqr (1, 0, fZZeroX, fZZeroY);
1001 }
1002 else
1003 {
1004 // Highpass - do the digital LP->HP transform on the poles and zeroes
1005 ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY);
1006 ComplexDiv (beta - fZZeroX, -fZZeroY, 1 - beta * fZZeroX, -beta * fZZeroY, &fZZeroX, &fZZeroY);
1007 fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist
1008 fDCPoleDistSqr /= Calc2D_DistSqr (-1, 0, fZZeroX, fZZeroY);
1009 }
1010 effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr;
1011 effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr;
1012 effect->mpBiquad[iPair]->fNumerCoeffs [2] = (square(fZZeroX) + square(fZZeroY)) * fDCPoleDistSqr;
1013 effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX;
1014 effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY);
1015 }
1016 // Now, if it's odd order, we have one more to do
1017 if (Order & 1)
1018 {
1019 int iPair = (Order-1)/2; // we'll do it as a biquad, but it's just first-order
1020 ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order)),
1021 cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order)),
1022 &fSPoleX, &fSPoleY);
1023 BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY);
1024 fZZeroX = -1; // in the s-plane, the zero is at infinity
1025 fZZeroY = 0;
1026 if (FilterSubtype == 0) // LOWPASS
1027 {
1028 fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY));
1029 fDCPoleDistSqr /= 2;
1030 }
1031 else
1032 {
1033 // Highpass - do the digital LP->HP transform on the poles and zeroes
1034 ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -fZPoleY, &fZPoleX, &fZPoleY);
1035 fZZeroX = 1;
1036 fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist
1037 fDCPoleDistSqr /= 2;
1038 }
1039 effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr;
1040 effect->mpBiquad[iPair]->fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr;
1041 effect->mpBiquad[iPair]->fNumerCoeffs [2] = 0;
1042 effect->mpBiquad[iPair]->fDenomCoeffs [0] = -fZPoleX;
1043 effect->mpBiquad[iPair]->fDenomCoeffs [1] = 0;
1044 }
1045 break;
1046 }
1047 effect->mOrder = Order; // ?? needed for ProcessOne to work in Preview. This probably should be done a different way, but how?
1048 return true;
1049 }
1050
1051 static double s_fChebyCoeffs [MAX_FILTER_ORDER][MAX_FILTER_ORDER+1] = {
1052 // For Chebyshev polynomials of the first kind (see http://en.wikipedia.org/wiki/Chebyshev_polynomial)
1053 // Coeffs are in the order 0, 1, 2...9
1054 {0, 1}, // order 1
1055 {-1, 0, 2}, // order 2 etc.
1056 {0, -3, 0, 4},
1057 {1, 0, -8, 0, 8},
1058 {0, 5, 0, -20, 0, 16},
1059 {-1, 0, 18, 0, -48, 0, 32},
1060 {0, -7, 0, 56, 0, -112, 0, 64},
1061 {1, 0, -32, 0, 160, 0, -256, 0, 128},
1062 {0, 9, 0, -120, 0, 432, 0, -576, 0, 256},
1063 {-1, 0, 50, 0, -400, 0, 1120, 0, -1280, 0, 512}
1064 };
1065
1066 static double ChebyPoly (int Order, double NormFreq) // NormFreq = 1 at the f0 point (where response is R dB down)
1067 {
1068 // Calc cosh (Order * acosh (NormFreq));
1069 double x = 1;
1070 double fSum = 0;
1071 wxASSERT (Order > 0 && Order <= MAX_FILTER_ORDER);
1072 for (int i = 0; i <= Order; i++)
1073 {
1074 fSum += s_fChebyCoeffs [Order-1][i] * x;
1075 x *= NormFreq;
1076 }
1077 return fSum;
1078 }
1079
1080 float ScienFilterDialog::FilterMagnAtFreq (float Freq)
1081 {
1082 float Magn;
1083 if (Freq >= mNyquist)
1084 Freq = mNyquist - 1; // prevent tan(PI/2)
1085 float FreqWarped = tan (PI * Freq/(2*mNyquist));
1086 if (Cutoff >= mNyquist)
1087 Cutoff = mNyquist - 1;
1088 float CutoffWarped = tan (PI * Cutoff/(2*mNyquist));
1089 float fOverflowThresh = pow (10.0, 12.0 / (2*Order)); // once we exceed 10^12 there's not much to be gained and overflow could happen
1090
1091 switch (FilterType)
1092 {
1093 case 0: // Butterworth
1094 default:
1095 switch (FilterSubtype)
1096 {
1097 case 0: // lowpass
1098 default:
1099 if (FreqWarped/CutoffWarped > fOverflowThresh) // prevent pow() overflow
1100 Magn = 0;
1101 else
1102 Magn = sqrt (1 / (1 + pow (FreqWarped/CutoffWarped, 2*Order)));
1103 break;
1104 case 1: // highpass
1105 if (FreqWarped/CutoffWarped > fOverflowThresh)
1106 Magn = 1;
1107 else
1108 Magn = sqrt (pow (FreqWarped/CutoffWarped, 2*Order) / (1 + pow (FreqWarped/CutoffWarped, 2*Order)));
1109 break;
1110 }
1111 break;
1112
1113 case 1: // Chebyshev Type 1
1114 double eps; eps = sqrt(pow (10.0, __max(0.001, Ripple)/10.0) - 1);
1115 switch (FilterSubtype)
1116 {
1117 case 0: // lowpass
1118 default:
1119 Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(Order, FreqWarped/CutoffWarped))));
1120 break;
1121 case 1:
1122 Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(Order, CutoffWarped/FreqWarped))));
1123 break;
1124 }
1125 break;
1126
1127 case 2: // Chebyshev Type 2
1128 eps = 1 / sqrt(pow (10.0, __max(0.001, StopbandRipple)/10.0) - 1);
1129 switch (FilterSubtype)
1130 {
1131 case 0: // lowpass
1132 default:
1133 Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(Order, CutoffWarped/FreqWarped)))));
1134 break;
1135 case 1:
1136 Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(Order, FreqWarped/CutoffWarped)))));
1137 break;
1138 }
1139 break;
1140 }
1141
1142 return Magn;
1143 }
1144
1145
1146
1147 // WDR: handler implementations for ScienFilterDialog
1148
1149 void ScienFilterDialog::OnOrder(wxCommandEvent &WXUNUSED(event))
1150 {
1151 Order = mFilterOrderCtl->GetSelection() + 1; // 0..n-1 -> 1..n
1152 mPanel->Refresh (false);
1153 }
1154
1155 void ScienFilterDialog::OnFilterType (wxCommandEvent &WXUNUSED(event))
1156 {
1157 FilterType = mFilterTypeCtl->GetSelection();
1158 EnableDisableRippleCtl (FilterType);
1159 mPanel->Refresh (false);
1160 }
1161
1162 void ScienFilterDialog::OnFilterSubtype (wxCommandEvent &WXUNUSED(event))
1163 {
1164 FilterSubtype = mFilterSubTypeCtl->GetSelection();
1165 mPanel->Refresh (false);
1166 }
1167
1168 void ScienFilterDialog::OnCutoff (wxCommandEvent &WXUNUSED(event))
1169 {
1170 double CutoffTemp;
1171 if (mCutoffCtl)
1172 {
1173 if (mCutoffCtl->GetValue().ToDouble(&CutoffTemp))
1174 {
1175 Cutoff = CutoffTemp;
1176 if (Cutoff >= mNyquist)
1177 {
1178 Cutoff = mNyquist - 1; // could handle Nyquist as a special case? eg. straight through if LPF
1179 mCutoffCtl->SetValue(Internat::ToDisplayString(Cutoff));
1180 }
1181 wxButton *ok = (wxButton *) FindWindow(wxID_OK);
1182 if (Cutoff < 0.1) // 0.1 Hz min
1183 {
1184 // Disable OK button
1185 ok->Enable(0);
1186 }
1187 else
1188 ok->Enable(1);
1189 }
1190 mPanel->Refresh (false);
1191 }
1192 }
1193
1194 void ScienFilterDialog::OnRipple (wxCommandEvent &WXUNUSED(event))
1195 {
1196 double RippleTemp;
1197 if (mRippleCtl)
1198 {
1199 if (mRippleCtl->GetValue().ToDouble(&RippleTemp))
1200 Ripple = RippleTemp;
1201 mPanel->Refresh (false);
1202 }
1203 }
1204
1205 void ScienFilterDialog::OnStopbandRipple (wxCommandEvent &WXUNUSED(event))
1206 {
1207 double RippleTemp;
1208 if (mStopbandRippleCtl)
1209 {
1210 if (mStopbandRippleCtl->GetValue().ToDouble(&RippleTemp))
1211 StopbandRipple = RippleTemp;
1212 mPanel->Refresh (false);
1213 }
1214 }
1215
1216 void ScienFilterDialog::OnSliderDBMIN(wxCommandEvent &WXUNUSED(event))
1217 {
1218 TransferGraphLimitsFromWindow();
1219 }
1220
1221 void ScienFilterDialog::OnSliderDBMAX(wxCommandEvent &WXUNUSED(event))
1222 {
1223 TransferGraphLimitsFromWindow();
1224 }
1225
1226
1227 void ScienFilterDialog::OnErase(wxEraseEvent &WXUNUSED(event))
1228 {
1229 // Ignore it
1230 }
1231
1232 void ScienFilterDialog::OnPaint(wxPaintEvent &WXUNUSED(event))
1233 {
1234 wxPaintDC dc(this);
1235
1236 #if defined(__WXGTK__)
1237 dc.SetBackground(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)));
1238 #endif
1239
1240 dc.Clear();
1241 }
1242
1243 void ScienFilterDialog::OnSize(wxSizeEvent &event)
1244 {
1245 Layout();
1246
1247 event.Skip();
1248 }
1249
1250 void ScienFilterDialog::OnPreview(wxCommandEvent &WXUNUSED(event))
1251 {
1252 CalcFilter (m_pEffect);
1253 m_pEffect->Preview();
1254 }
1255
1256
1257 void ScienFilterDialog::Finish(bool ok)
1258 {
1259 mPanel = NULL;
1260 EndModal(ok);
1261 }
1262
1263 void ScienFilterDialog::OnCancel(wxCommandEvent &WXUNUSED(event))
1264 {
1265 Finish(false);
1266 }
1267
1268 void ScienFilterDialog::OnOk(wxCommandEvent &event)
1269 {
1270 CalcFilter (m_pEffect);
1271
1272 if( Validate() )
1273 {
1274 Finish(true);
1275 }
1276 else
1277 {
1278 event.Skip(false);
1279 }
1280 }
1281
1282 void ScienFilterDialog::EnableDisableRippleCtl (int FilterType)
1283 {
1284 if (FilterType == 0) // Butterworth
1285 {
1286 szrPass->Show(false);
1287 szrStop->Show(false);
1288 }
1289 else if (FilterType == 1) // Chebyshev Type1
1290 {
1291 szrPass->Show(true);
1292 szrStop->Show(false);
1293 }
1294 else // Chebyshev Type2
1295 {
1296 szrPass->Show(false);
1297 szrStop->Show(true);
1298 }
1299 wxSizeEvent dummy;
1300 OnSize(dummy);
1301 }
1302
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 KeyView.cpp
5
6 *******************************************************************//*!
7
8 \class KeyView
9 \brief Provides multiple views of keyboard shortcuts
10
11 *//*********************************************************************/
12
13 #include "../Audacity.h"
14
15 #include <wx/defs.h>
16 #include <wx/settings.h>
17 #include <wx/vlbox.h>
18
19 #include "../AColor.h"
20 #include "../ShuttleGui.h"
21 #include "../commands/CommandManager.h"
22 #include "../commands/Keyboard.h"
23 #include "KeyView.h"
24
25 #include <wx/arrimpl.cpp>
26
27 // Various drawing constants
28 #define KV_BITMAP_SIZE 16
29 #define KV_LEFT_MARGIN 2
30 #define KV_COLUMN_SPACER 5
31 #define KV_VSCROLL_WIDTH 16 /* figure this out automatically? */
32
33 // Define the KeyNode arrays
34 WX_DEFINE_OBJARRAY(KeyNodeArray);
35 WX_DEFINE_OBJARRAY(KeyNodeArrayPtr);
36
37 // Define the event table
38 BEGIN_EVENT_TABLE(KeyView, wxVListBox)
39 EVT_LEFT_DOWN(KeyView::OnLeftDown)
40 EVT_KEY_DOWN(KeyView::OnKeyDown)
41 EVT_LISTBOX(wxID_ANY, KeyView::OnSelected)
42 EVT_SET_FOCUS(KeyView::OnSetFocus)
43 EVT_KILL_FOCUS(KeyView::OnKillFocus)
44 EVT_SIZE(KeyView::OnSize)
45 EVT_SCROLLWIN(KeyView::OnScroll)
46 END_EVENT_TABLE();
47
48 // ============================================================================
49 // KeyView class
50 // ============================================================================
51 KeyView::KeyView(wxWindow *parent,
52 wxWindowID id,
53 const wxPoint & pos,
54 const wxSize & size)
55 : wxVListBox(parent, id, pos, size, wxBORDER_THEME),
56 mOpen(NULL),
57 mClosed(NULL),
58 mScrollX(0),
59 mWidth(0)
60 {
61 #if wxUSE_ACCESSIBILITY
62 // Create and set accessibility object
63 mAx = new KeyViewAx(this);
64 SetAccessible(mAx);
65 #endif
66
67 // Create the device context
68 wxMemoryDC dc;
69
70 // Create the open/expanded bitmap
71 mOpen = new wxBitmap(16, 16);
72 dc.SelectObject(*mOpen);
73
74 dc.SetBrush(*wxWHITE_BRUSH);
75 dc.SetPen(*wxWHITE_PEN);
76 dc.DrawRectangle(0, 0, 16, 16);
77
78 dc.SetPen(*wxBLACK_PEN);
79 dc.DrawRectangle(3, 4, 9, 9);
80 dc.DrawLine(5, 8, 10, 8);
81 dc.SelectObject(wxNullBitmap);
82
83 // Create the closed/collapsed bitmap
84 mClosed = new wxBitmap(16, 16);
85 dc.SelectObject(*mClosed);
86
87 dc.SetBrush(*wxWHITE_BRUSH);
88 dc.SetPen(*wxWHITE_PEN);
89 dc.DrawRectangle(0, 0, 16, 16);
90
91 dc.SetPen(*wxBLACK_PEN);
92 dc.DrawRectangle(3, 4, 9, 9);
93 dc.DrawLine(7, 6, 7, 11);
94 dc.DrawLine(5, 8, 10, 8);
95 dc.SelectObject(wxNullBitmap);
96
97 // The default view
98 mViewType = ViewByTree;
99 }
100
101 KeyView::~KeyView()
102 {
103 // Cleanup
104 if (mOpen)
105 {
106 delete mOpen;
107 }
108
109 if (mClosed)
110 {
111 delete mClosed;
112 }
113 }
114
115 //
116 // Returns the index of the selected node
117 //
118 int
119 KeyView::GetSelected() const
120 {
121 return LineToIndex(GetSelection());
122 }
123
124 //
125 // Returns the name of the control
126 //
127 wxString
128 KeyView::GetName() const
129 {
130 // Just forward request
131 return wxVListBox::GetName();
132 }
133
134 //
135 // Returns the label for the given index
136 //
137 wxString
138 KeyView::GetLabel(int index) const
139 {
140 // Make sure index is valid
141 if (index < 0 || index >= (int) mNodes.GetCount())
142 {
143 wxASSERT(false);
144 return wxEmptyString;
145 }
146
147 return mNodes[index].label;
148 }
149
150 //
151 // Returns the prefix (if available) prepended to the label for the given index
152 //
153 wxString
154 KeyView::GetFullLabel(int index) const
155 {
156 // Make sure index is valid
157 if (index < 0 || index >= (int) mNodes.GetCount())
158 {
159 wxASSERT(false);
160 return wxEmptyString;
161 }
162
163 // Cache the node and label
164 KeyNode & node = mNodes[index];
165 wxString label = node.label;
166
167 // Prepend the prefix if available
168 if (!node.prefix.IsEmpty())
169 {
170 label = node.prefix + wxT(" - ") + label;
171 }
172
173 return label;
174 }
175
176 //
177 // Returns the index for the given name
178 //
179 int
180 KeyView::GetIndexByName(const wxString & name) const
181 {
182 int cnt = (int) mNodes.GetCount();
183
184 // Search the nodes for the key
185 for (int i = 0; i < cnt; i++)
186 {
187 if (name.CmpNoCase(mNodes[i].name) == 0)
188 {
189 return mNodes[i].index;
190 }
191 }
192
193 return wxNOT_FOUND;
194 }
195
196 //
197 // Returns the command manager name for the given index
198 //
199 wxString
200 KeyView::GetName(int index) const
201 {
202 // Make sure index is valid
203 if (index < 0 || index >= (int) mNodes.GetCount())
204 {
205 wxASSERT(false);
206 return wxEmptyString;
207 }
208
209 return mNodes[index].name;
210 }
211
212 //
213 // Returns the command manager index for the given key combination
214 //
215 wxString
216 KeyView::GetNameByKey(const wxString & key) const
217 {
218 int cnt = (int) mNodes.GetCount();
219
220 // Search the nodes for the key
221 for (int i = 0; i < cnt; i++)
222 {
223 if (key.CmpNoCase(mNodes[i].key) == 0)
224 {
225 return mNodes[i].name;
226 }
227 }
228
229 return wxEmptyString;
230 }
231
232 //
233 // Returns the index for the given key
234 //
235 int
236 KeyView::GetIndexByKey(const wxString & key) const
237 {
238 int cnt = (int) mNodes.GetCount();
239
240 // Search the nodes for the key
241 for (int i = 0; i < cnt; i++)
242 {
243 if (key.CmpNoCase(mNodes[i].key) == 0)
244 {
245 return mNodes[i].index;
246 }
247 }
248
249 return wxNOT_FOUND;
250 }
251
252 //
253 // Returns the key for the given index
254 //
255 wxString
256 KeyView::GetKey(int index) const
257 {
258 // Make sure index is valid
259 if (index < 0 || index >= (int) mNodes.GetCount())
260 {
261 wxASSERT(false);
262 return wxEmptyString;
263 }
264
265 return mNodes[index].key;
266 }
267
268 //
269 // Use to determine if a key can be assigned to the given index
270 //
271 bool
272 KeyView::CanSetKey(int index) const
273 {
274 // Make sure index is valid
275 if (index < 0 || index >= (int) mNodes.GetCount())
276 {
277 wxASSERT(false);
278 return false;
279 }
280
281 // Parents can't be assigned keys
282 return !mNodes[index].isparent;
283 }
284
285 //
286 // Sets the key for the given index
287 //
288 bool
289 KeyView::SetKey(int index, const wxString & key)
290 {
291 // Make sure index is valid
292 if (index < 0 || index >= (int) mNodes.GetCount())
293 {
294 wxASSERT(false);
295 return false;
296 }
297
298 // Cache the node
299 KeyNode & node = mNodes[index];
300
301 // Do not allow setting keys on branches
302 if (node.isparent)
303 {
304 return false;
305 }
306
307 // Set the new key
308 node.key = key;
309
310 // Check to see if the key column needs to be expanded
311 int x, y;
312 GetTextExtent(node.key, &x, &y);
313 if (x > mKeyWidth || y > mLineHeight)
314 {
315 // New key is wider than column so recalc extents (will refresh view)
316 RecalcExtents();
317 return true;
318 }
319
320 // Refresh the view lines
321 RefreshAll();
322
323 return true;
324 }
325
326 //
327 // Sets the key for the given name
328 //
329 bool
330 KeyView::SetKeyByName(const wxString & name, const wxString & key)
331 {
332 int index = GetIndexByName(name);
333
334 // Bail is the name wasn't found
335 if (index == wxNOT_FOUND)
336 {
337 return false;
338 }
339
340 // Go set the key
341 return SetKey(index, key);
342 }
343
344 //
345 // Sets the view type
346 //
347 void
348 KeyView::SetView(ViewByType type)
349 {
350 int index = LineToIndex(GetSelection());
351
352 // Handle an existing selection
353 if (index != wxNOT_FOUND)
354 {
355 // Cache the currently selected node
356 KeyNode & node = mNodes[index];
357
358 // Expand branches if switching to Tree view and a line
359 // is currently selected
360 if (type == ViewByTree)
361 {
362 // Cache the node's depth
363 int depth = node.depth;
364
365 // Search for its parents, setting each one as open
366 for (int i = node.index - 1; i >= 0 && depth > 1; i--)
367 {
368 if (mNodes[i].depth < depth)
369 {
370 mNodes[i].isopen = true;
371 depth = mNodes[i].depth;
372 }
373 }
374 }
375 }
376
377 // Unselect any currently selected line...do even if none selected
378 SelectNode(-1);
379
380 // Save new type
381 mViewType = type;
382
383 // Refresh the view lines
384 RefreshLines();
385
386 // Reselect old node (if possible)
387 if (index != wxNOT_FOUND)
388 {
389 SelectNode(index);
390 }
391
392 return;
393 }
394
395 //
396 // Sets the filter
397 //
398 void
399 KeyView::SetFilter(const wxString & filter)
400 {
401 int index = LineToIndex(GetSelection());
402
403 // Unselect any currently selected line...do even if none selected
404 SelectNode(-1);
405
406 // Save the filter
407 mFilter = filter.Lower();
408
409 // Refresh the view lines
410 RefreshLines();
411
412 // Reselect old node (if possible)
413 if (index != wxNOT_FOUND)
414 {
415 SelectNode(index);
416 }
417 }
418
419 //
420 // Expand all branches
421 //
422 void
423 KeyView::ExpandAll()
424 {
425 int cnt = (int) mNodes.GetCount();
426
427 // Set all parent nodes to open
428 for (int i = 0; i < cnt; i++)
429 {
430 KeyNode & node = mNodes[i];
431
432 if (node.isparent)
433 {
434 node.isopen = true;
435 }
436 }
437
438 RefreshLines();
439 }
440
441 //
442 // Collapse all branches
443 //
444 void
445 KeyView::CollapseAll()
446 {
447 int cnt = (int) mNodes.GetCount();
448
449 // Set all parent nodes to closed
450 for (int i = 0; i < cnt; i++)
451 {
452 KeyNode & node = mNodes[i];
453
454 if (node.isparent)
455 {
456 node.isopen = false;
457 }
458 }
459
460 RefreshLines();
461 }
462
463 //
464 // Recalculate the measurements used for columns and scrolling
465 //
466 void
467 KeyView::RecalcExtents()
468 {
469 // Reset
470 mLineHeight = 0;
471 mCommandWidth = 0;
472 mKeyWidth = 0;
473
474 // Examine all nodes
475 int cnt = (int) mNodes.GetCount();
476 for (int i = 0; i < cnt; i++)
477 {
478 KeyNode & node = mNodes[i];
479 int x, y;
480
481 if (node.iscat)
482 {
483 // Measure the category
484 GetTextExtent(node.category, &x, &y);
485 }
486 else if (node.ispfx)
487 {
488 // Measure the prefix
489 GetTextExtent(node.prefix, &x, &y);
490 }
491 else
492 {
493 // Measure the key
494 GetTextExtent(node.key, &x, &y);
495 mLineHeight = wxMax(mLineHeight, y);
496 mKeyWidth = wxMax(mKeyWidth, x);
497
498 // Prepend prefix for view types other than tree
499 wxString label = node.label;
500 if (mViewType != ViewByTree && !node.prefix.IsEmpty())
501 {
502 label = node.prefix + wxT(" - ") + label;
503 }
504
505 // Measure the label
506 GetTextExtent(label, &x, &y);
507 }
508
509 // Finish calc for command column
510 mLineHeight = wxMax(mLineHeight, y);
511 mCommandWidth = wxMax(mCommandWidth, x);
512 }
513
514 // Update horizontal scrollbar
515 UpdateHScroll();
516 }
517
518 //
519 // Update the horizontal scrollbar or remove it if not needed
520 //
521 void
522 KeyView::UpdateHScroll()
523 {
524 // Get the internal dimensions of the view
525 wxRect r = GetClientRect();
526
527 // Calculate the full line width
528 mWidth = KV_LEFT_MARGIN +
529 mCommandWidth +
530 KV_COLUMN_SPACER +
531 mKeyWidth +
532 KV_VSCROLL_WIDTH;
533
534 // Retrieve the current horizontal scroll amount
535 mScrollX = GetScrollPos(wxHORIZONTAL);
536
537 if (mWidth <= r.GetWidth())
538 {
539 // Remove the scrollbar if it will fit within client width
540 SetScrollbar(wxHORIZONTAL, 0, 0, 0);
541 }
542 else
543 {
544 // Set scrollbar metrics
545 SetScrollbar(wxHORIZONTAL, mScrollX, r.GetWidth(), mWidth);
546 }
547
548 // Refresh the entire view
549 RefreshAll();
550 }
551
552 //
553 // Process a new set of bindings
554 //
555 void
556 KeyView::RefreshBindings(const wxArrayString & names,
557 const wxArrayString & categories,
558 const wxArrayString & prefixes,
559 const wxArrayString & labels,
560 const wxArrayString & keys)
561 {
562 bool firsttime = mNodes.GetCount() == 0;
563
564 // Start clean
565 mNodes.Clear();
566
567 // Same as in RecalcExtents() but do it inline
568 mLineHeight = 0;
569 mKeyWidth = 0;
570 mCommandWidth = 0;
571
572 wxString lastcat;
573 wxString lastpfx;
574 int nodecnt = 0;
575 int depth = 1;
576 bool incat = false;
577 bool inpfx = false;
578
579 // Examine all names...all arrays passed have the same indexes
580 int cnt = (int) names.GetCount();
581 for (int i = 0; i < cnt; i++)
582 {
583 wxString name = names[i];
584 int x, y;
585
586 // Remove any menu code from the category and prefix
587 wxString cat = wxMenuItem::GetLabelFromText(categories[i]);
588 wxString pfx = wxMenuItem::GetLabelFromText(prefixes[i]);
589
590 // Append "Menu" this node is for a menu title
591 if (cat != wxT("Command"))
592 {
593 cat.Append(wxT(" "));
594 cat += _("Menu");
595 }
596
597 // Process a new category
598 if (cat != lastcat)
599 {
600 // A new category always finishes any current subtree
601 if (inpfx)
602 {
603 // Back to category level
604 depth--;
605 inpfx = false;
606 }
607
608 // Only time this is not true is during the first iteration
609 if (incat)
610 {
611 // Back to root level
612 depth--;
613 incat = false;
614 }
615
616 // Remember for next iteration
617 lastcat = cat;
618
619 // Add a new category node
620 if (cat != wxEmptyString)
621 {
622 KeyNode node;
623
624 // Fill in the node info
625 node.name = wxEmptyString; // don't associate branches with a command
626 node.category = cat;
627 node.prefix = pfx;
628 node.label = cat;
629 node.index = nodecnt++;
630 node.iscat = true;
631 node.isparent = true;
632 node.depth = depth++;
633
634 // Add it to the tree
635 mNodes.Add(node);
636 incat = true;
637
638 // Measure category
639 GetTextExtent(cat, &x, &y);
640 mLineHeight = wxMax(mLineHeight, y);
641 mCommandWidth = wxMax(mCommandWidth, x);
642 }
643 }
644
645 // Process a new prefix
646 if (pfx != lastpfx)
647 {
648 // Done with prefix branch
649 if (inpfx)
650 {
651 depth--;
652 inpfx = false;
653 }
654
655 // Remember for next iteration
656 lastpfx = pfx;
657
658 // Add a new prefix node
659 if (pfx != wxEmptyString)
660 {
661 KeyNode node;
662
663 // Fill in the node info
664 node.name = wxEmptyString; // don't associate branches with a command
665 node.category = cat;
666 node.prefix = pfx;
667 node.label = pfx;
668 node.index = nodecnt++;
669 node.ispfx = true;
670 node.isparent = true;
671 node.depth = depth++;
672
673 // Add it to the tree
674 mNodes.Add(node);
675 inpfx = true;
676 }
677 }
678
679 // Add the key entry
680 KeyNode node;
681 node.category = cat;
682 node.prefix = pfx;
683
684 // Labels for undo and redo change according to the last command
685 // which can be undone/redone, so give them a special check in order
686 // not to confuse users
687 if (name == wxT("Undo"))
688 {
689 node.label = _("Undo");
690 }
691 else if (name == wxT("Redo"))
692 {
693 node.label = _("Redo");
694 }
695 else
696 {
697 // Strip any menu codes from label
698 node.label = wxMenuItem::GetLabelFromText(labels[i].BeforeFirst(wxT('\t')));
699 }
700
701 // Fill in remaining info
702 node.name = name;
703 node.key = KeyStringDisplay(keys[i]);
704 node.index = nodecnt++;
705 node.depth = depth;
706
707 // Add it to the tree
708 mNodes.Add(node);
709
710 // Measure key
711 GetTextExtent(node.key, &x, &y);
712 mLineHeight = wxMax(mLineHeight, y);
713 mKeyWidth = wxMax(mKeyWidth, x);
714
715 // Prepend prefix for all view types to determine maximum
716 // column widths
717 wxString label = node.label;
718 if (!node.prefix.IsEmpty())
719 {
720 label = node.prefix + wxT(" - ") + label;
721 }
722
723 // Measure label
724 GetTextExtent(label, &x, &y);
725 mLineHeight = wxMax(mLineHeight, y);
726 mCommandWidth = wxMax(mCommandWidth, x);
727 }
728
729 #if 0
730 // For debugging
731 for (int j = 0; j < mNodes.GetCount(); j++)
732 {
733 KeyNode & node = mNodes[j];
734 wxLogDebug(wxT("NODE line %4d index %4d depth %1d open %1d parent %1d cat %1d pfx %1d name %s STR %s | %s | %s"),
735 node.line,
736 node.index,
737 node.depth,
738 node.isopen,
739 node.isparent,
740 node.iscat,
741 node.ispfx,
742 node.name.c_str(),
743 node.category.c_str(),
744 node.prefix.c_str(),
745 node.label.c_str());
746 }
747 #endif
748
749 // Update horizontal scrollbar
750 UpdateHScroll();
751
752 // Refresh the view lines
753 RefreshLines();
754
755 // Set the selected node if this was the first time through
756 if (firsttime)
757 {
758 SelectNode(LineToIndex(0));
759 }
760 }
761
762 //
763 // Refresh the list of lines within the current view
764 //
765 void
766 KeyView::RefreshLines()
767 {
768 int cnt = (int) mNodes.GetCount();
769 int linecnt = 0;
770 mLines.Empty();
771
772 // Process a filter if one is set
773 if (!mFilter.IsEmpty())
774 {
775 // Examine all nodes
776 for (int i = 0; i < cnt; i++)
777 {
778 KeyNode & node = mNodes[i];
779
780 // Reset line number
781 node.line = wxNOT_FOUND;
782
783 // Search columns based on view type
784 wxString searchit;
785 switch (mViewType)
786 {
787 // The x"01" separator is used to prevent finding a
788 // match comprising the end of the label and beginning
789 // of the key. It was chosen since it's not very likely
790 // to appear in the filter itself.
791 case ViewByTree:
792 searchit = node.label.Lower() +
793 wxT("\01x") +
794 node.key.Lower();
795 break;
796
797 case ViewByName:
798 searchit = node.label.Lower();
799 break;
800
801 case ViewByKey:
802 searchit = node.key.Lower();
803 break;
804 }
805 if (searchit.Find(mFilter) == wxNOT_FOUND)
806 {
807 // Not found so continue to next node
808 continue;
809 }
810
811 // For the Key View, if the filter is a single character,
812 // then it has to be the last character in the searchit string,
813 // and be preceded by nothing or +.
814 if ((mViewType == ViewByKey) &&
815 (mFilter.Len() == 1) &&
816 (!mFilter.IsSameAs(searchit.Last()) ||
817 ((searchit.Len() > 1) &&
818 ((wxString)(searchit.GetChar(searchit.Len() - 2)) != wxT("+")))))
819 {
820 // Not suitable so continue to next node
821 continue;
822 }
823
824 // For tree view, we must make sure all parent nodes are included
825 // whether they match the filter or not.
826 if (mViewType == ViewByTree)
827 {
828 KeyNodeArrayPtr queue;
829 int depth = node.depth;
830
831 // This node is a category or prefix node, so always mark them
832 // as open.
833 //
834 // What this is really doing is resolving a situation where the
835 // the filter matches a parent node and nothing underneath. In
836 // this case, the node would never be marked as open.
837 if (node.isparent)
838 {
839 node.isopen = true;
840 }
841
842 // Examine siblings until a parent is found.
843 for (int j = node.index - 1; j >= 0 && depth > 0; j--)
844 {
845 // Found a parent
846 if (mNodes[j].depth < depth)
847 {
848 // Examine all previously added nodes to see if this nodes
849 // ancestors need to be added prior to adding this node.
850 bool found = false;
851 for (int k = (int) mLines.GetCount() - 1; k >= 0; k--)
852 {
853 // The node indexes match, so we've found the parent of the
854 // child node.
855 if (mLines[k]->index == mNodes[j].index)
856 {
857 found = true;
858 break;
859 }
860 }
861
862 // The parent wasn't found so remember it for later
863 // addition. Can't add directory to mLines here since
864 // they will wind up in reverse order.
865 if (!found)
866 {
867 queue.Add(&mNodes[j]);
868 }
869
870 // Traverse up the tree
871 depth = mNodes[j].depth;
872 }
873 }
874
875 // Add any queues nodes to list. This will all be
876 // parent nodes, so mark them as open.
877 for (int j = (int) queue.GetCount() - 1; j >= 0; j--)
878 {
879 queue[j]->isopen = true;
880 queue[j]->line = linecnt++;
881 mLines.Add(queue[j]);
882 }
883 }
884
885 // Finally add the child node
886 node.line = linecnt++;
887 mLines.Add(&node);
888 }
889 }
890 else
891 {
892 // Examine all nodes - non-filtered
893 for (int i = 0; i < cnt; i++)
894 {
895 KeyNode & node = mNodes[i];
896
897 // Reset line number
898 node.line = wxNOT_FOUND;
899
900 // Node is either a category or prefix
901 if (node.isparent)
902 {
903 // Only need to do this for tree views
904 if (mViewType != ViewByTree)
905 {
906 continue;
907 }
908
909 // Add the node
910 node.line = linecnt++;
911 mLines.Add(&node);
912
913 // If this node is not open, then skip all of it's decendants
914 if (!node.isopen)
915 {
916 bool iscat = node.iscat;
917 bool ispfx = node.ispfx;
918
919 // Skip nodes until we find a node that has a different
920 // category or prefix
921 while (i < cnt)
922 {
923 KeyNode & skip = mNodes[i];
924
925 if ((iscat && skip.category != node.category) ||
926 (ispfx && skip.prefix != node.prefix))
927 {
928 break;
929 }
930
931 // Bump to next node
932 i++;
933 }
934
935 // Index is pointing to the node that was different or
936 // past the end, so back off to last node of this branch.
937 i--;
938 }
939 continue;
940 }
941
942 // Add child node to list
943 node.line = linecnt++;
944 mLines.Add(&node);
945 }
946 }
947
948 // Sort list based on type
949 switch (mViewType)
950 {
951 case ViewByTree:
952 mLines.Sort(CmpKeyNodeByTree);
953 break;
954
955 case ViewByName:
956 mLines.Sort(CmpKeyNodeByName);
957 break;
958
959 case ViewByKey:
960 mLines.Sort(CmpKeyNodeByKey);
961 break;
962 }
963
964 // Now, reassign the line numbers
965 for (int i = 0; i < (int) mLines.GetCount(); i++)
966 {
967 mLines[i]->line = i;
968 }
969
970 #if 0
971 // For debugging
972 for (int j = 0; j < mLines.GetCount(); j++)
973 {
974 KeyNode & node = *mLines[j];
975 wxLogDebug(wxT("LINE line %4d index %4d depth %1d open %1d parent %1d cat %1d pfx %1d name %s STR %s | %s | %s"),
976 node.line,
977 node.index,
978 node.depth,
979 node.isopen,
980 node.isparent,
981 node.iscat,
982 node.ispfx,
983 node.name.c_str(),
984 node.category.c_str(),
985 node.prefix.c_str(),
986 node.label.c_str());
987 }
988 #endif
989
990 // Tell listbox the new count and refresh the entire view
991 SetItemCount(mLines.GetCount());
992 RefreshAll();
993
994 #if wxUSE_ACCESSIBILITY
995 // Let accessibility know that the list has changed
996 mAx->ListUpdated();
997 #endif
998 }
999
1000 //
1001 // Select a node
1002 //
1003 // Parameter can be wxNOT_FOUND to clear selection
1004 //
1005 void
1006 KeyView::SelectNode(int index)
1007 {
1008 int line = IndexToLine(index);
1009
1010 // Tell the listbox to select the line
1011 SetSelection(line);
1012
1013 #if wxUSE_ACCESSIBILITY
1014 // And accessibility
1015 mAx->SetCurrentLine(line);
1016 #endif
1017
1018 // Always send an event to let parent know of selection change
1019 //
1020 // Must do this ourselves becuase we want to send notifications
1021 // even if there isn't an item selected and SendSelectedEvent()
1022 // doesn't allow sending an event for indexes not in the listbox.
1023 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, GetId());
1024 event.SetEventObject(this);
1025 event.SetInt(line);
1026 (void)GetEventHandler()->ProcessEvent(event);
1027 }
1028
1029 //
1030 // Converts a line index to a node index
1031 //
1032 int
1033 KeyView::LineToIndex(int line) const
1034 {
1035 if (line < 0 || line >= (int) mLines.GetCount())
1036 {
1037 return wxNOT_FOUND;
1038 }
1039
1040 return mLines[line]->index;
1041 }
1042
1043 //
1044 // Converts a node index to a line index
1045 //
1046 int
1047 KeyView::IndexToLine(int index) const
1048 {
1049 if (index < 0 || index >= (int) mNodes.GetCount())
1050 {
1051 return wxNOT_FOUND;
1052 }
1053
1054 return mNodes[index].line;
1055 }
1056
1057 //
1058 // Draw the background for a given line
1059 //
1060 // This is called by the listbox when it needs to redraw the view.
1061 //
1062 void
1063 KeyView::OnDrawBackground(wxDC & dc, const wxRect & rect, size_t line) const
1064 {
1065 const KeyNode *node = mLines[line];
1066 wxRect r = rect;
1067 wxCoord indent = 0;
1068
1069 // When in tree view mode, each younger branch gets indented by the
1070 // width of the open/close bitmaps
1071 if (mViewType == ViewByTree)
1072 {
1073 indent += node->depth * KV_BITMAP_SIZE;
1074 }
1075
1076 // Offset left side by the indentation (if any) and scroll amounts
1077 r.x = indent - mScrollX;
1078
1079 // If the line width is less than the client width, then we want to
1080 // extend the background to the right edge of the client view. Otherwise,
1081 // go all the way to the end of the line width...this will draw past the
1082 // right edge, but that's what we want.
1083 r.width = wxMax(mWidth, r.width);
1084
1085 // Selected lines get a solid background
1086 if (IsSelected(line))
1087 {
1088 if (FindFocus() == this)
1089 {
1090 // Focused lines get highlighted background
1091 dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)));
1092 AColor::DrawFocus(dc, r);
1093 dc.DrawRectangle(r);
1094 }
1095 else
1096 {
1097 // Non ocused lines get a light background
1098 dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)));
1099 dc.SetPen(*wxTRANSPARENT_PEN);
1100 dc.DrawRectangle(r);
1101 }
1102 }
1103 else
1104 {
1105 // Non-selected lines get a thin bottom border
1106 dc.SetPen(wxColour(240, 240, 240));
1107 dc.DrawLine(r.GetLeft(), r.GetBottom(), r.GetRight(), r.GetBottom());
1108 }
1109 }
1110
1111 //
1112 // Draw a line
1113 //
1114 // This is called by the listbox when it needs to redraw the view.
1115 //
1116 void
1117 KeyView::OnDrawItem(wxDC & dc, const wxRect & rect, size_t line) const
1118 {
1119 const KeyNode *node = mLines[line];
1120 wxString label = node->label;
1121
1122 // Make sure the DC has a valid font
1123 dc.SetFont(GetFont());
1124
1125 // Set the text color based on selection and focus
1126 if (IsSelected(line) && FindFocus() == this)
1127 {
1128 dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT));
1129 }
1130 else
1131 {
1132 dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT));
1133 }
1134
1135 // Tree views get bitmaps
1136 if (mViewType == ViewByTree)
1137 {
1138 // Adjust left edge to account for scrolling
1139 wxCoord x = rect.x - mScrollX;
1140
1141 if (node->iscat)
1142 {
1143 // Draw categories bitmap at left edge
1144 dc.DrawBitmap(node->isopen ? *mOpen : *mClosed, x, rect.y);
1145 }
1146 else if (node->ispfx)
1147 {
1148 // Draw prefix bitmap to the right of the category bitmap
1149 dc.DrawBitmap(node->isopen ? *mOpen : *mClosed, x + KV_BITMAP_SIZE, rect.y);
1150 }
1151
1152 // Indent text
1153 x += KV_LEFT_MARGIN;
1154
1155 // Draw the command and key columns
1156 dc.DrawText(label, x + node->depth * KV_BITMAP_SIZE, rect.y);
1157 dc.DrawText(node->key, x + mCommandWidth + KV_COLUMN_SPACER, rect.y);
1158 }
1159 else
1160 {
1161 // Adjust left edge by margin and account for scrolling
1162 wxCoord x = rect.x + KV_LEFT_MARGIN - mScrollX;
1163
1164 // Prepend prefix if available
1165 if (!node->prefix.IsEmpty())
1166 {
1167 label = node->prefix + wxT(" - ") + label;
1168 }
1169
1170 // Swap the columns based on view type
1171 if(mViewType == ViewByName)
1172 {
1173 // Draw command column and then key column
1174 dc.DrawText(label, x, rect.y);
1175 dc.DrawText(node->key, x + mCommandWidth + KV_COLUMN_SPACER, rect.y);
1176 }
1177 else if(mViewType == ViewByKey)
1178 {
1179 // Draw key columnd and then command column
1180 dc.DrawText(node->key, x, rect.y);
1181 dc.DrawText(label, x + mKeyWidth + KV_COLUMN_SPACER, rect.y);
1182 }
1183 }
1184
1185 return;
1186 }
1187
1188 //
1189 // Provide the height of the given line
1190 //
1191 // This is called by the listbox when it needs to redraw the view.
1192 //
1193 wxCoord
1194 KeyView::OnMeasureItem(size_t WXUNUSED(line)) const
1195 {
1196 // All lines are of equal height
1197 //
1198 // (add a magic 1 for decenders...looks better...not required)
1199 return mLineHeight + 1;
1200 }
1201
1202 //
1203 // Handle the wxEVT_LISTBOX event
1204 //
1205 void
1206 KeyView::OnSelected(wxCommandEvent & event)
1207 {
1208 // Allow further processing
1209 event.Skip();
1210
1211 #if wxUSE_ACCESSIBILITY
1212 // Tell accessibility of the change
1213 mAx->SetCurrentLine(event.GetInt());
1214 #endif
1215 }
1216
1217 //
1218 // Handle the wxEVT_SET_FOCUS event
1219 //
1220 void
1221 KeyView::OnSetFocus(wxFocusEvent & event)
1222 {
1223 // Allow further processing
1224 event.Skip();
1225
1226 // Refresh the selected line to pull in any changes while
1227 // focus was away...like when setting a new key value. This
1228 // will also refresh the visual (highlighted) state.
1229 if (GetSelection() != wxNOT_FOUND)
1230 {
1231 RefreshLine(GetSelection());
1232 }
1233
1234 #if wxUSE_ACCESSIBILITY
1235 // Tell accessibility of the change
1236 mAx->SetCurrentLine(GetSelection());
1237 #endif
1238 }
1239
1240 //
1241 // Handle the wxEVT_KILL_FOCUS event
1242 //
1243 void
1244 KeyView::OnKillFocus(wxFocusEvent & event)
1245 {
1246 // Allow further processing
1247 event.Skip();
1248
1249 // Refresh the selected line to adjust visual highlighting.
1250 if (GetSelection() != wxNOT_FOUND)
1251 {
1252 RefreshLine(GetSelection());
1253 }
1254 }
1255
1256 //
1257 // Handle the wxEVT_SIZE event
1258 //
1259 void
1260 KeyView::OnSize(wxSizeEvent & WXUNUSED(event))
1261 {
1262 // Update horizontal scrollbar
1263 UpdateHScroll();
1264 }
1265
1266 //
1267 // Handle the wxEVT_SCROLL event
1268 //
1269 void
1270 KeyView::OnScroll(wxScrollWinEvent & event)
1271 {
1272 // We only care bout the horizontal scrollbar.
1273 if (event.GetOrientation() != wxHORIZONTAL)
1274 {
1275 // Allow further processing
1276 event.Skip();
1277 return;
1278 }
1279
1280 // Get new scroll position and scroll the view
1281 mScrollX = event.GetPosition();
1282 SetScrollPos(wxHORIZONTAL, mScrollX);
1283
1284 // Refresh the entire view
1285 RefreshAll();
1286 }
1287
1288 //
1289 // Handle the wxEVT_KEY_DOWN event
1290 //
1291 void
1292 KeyView::OnKeyDown(wxKeyEvent & event)
1293 {
1294 int line = GetSelection();
1295
1296 int keycode = event.GetKeyCode();
1297 switch (keycode)
1298 {
1299 // The LEFT key moves selection to parent or collapses selected
1300 // node if it is expanded.
1301 case WXK_LEFT:
1302 {
1303 // Nothing selected...nothing to do
1304 if (line == wxNOT_FOUND)
1305 {
1306 // Allow further processing
1307 event.Skip();
1308 break;
1309 }
1310
1311 KeyNode *node = mLines[line];
1312
1313 // Collapse the node if it is open
1314 if (node->isopen)
1315 {
1316 // No longer open
1317 node->isopen = false;
1318
1319 // Don't want the view to scroll vertically, so remember the current
1320 // top line.
1321 size_t topline = GetVisibleBegin();
1322
1323 // Refresh the view now that the number of lines have changed
1324 RefreshLines();
1325
1326 // Reset the original top line
1327 ScrollToLine(topline);
1328
1329 // And make sure current line is still selected
1330 SelectNode(LineToIndex(line));
1331 }
1332 else
1333 {
1334 // Move selection to the parent of this node
1335 for (int i = line - 1; i >= 0; i--)
1336 {
1337 // Found the parent
1338 if (mLines[i]->depth < node->depth)
1339 {
1340 // So select it
1341 SelectNode(LineToIndex(i));
1342 break;
1343 }
1344 }
1345 }
1346
1347 // Further processing of the event is not wanted
1348 // (we didn't call event.Skip()
1349 }
1350 break;
1351
1352 // The RIGHT key moves the selection to the first child or expands
1353 // the node if it is a parent.
1354 case WXK_RIGHT:
1355 {
1356 // Nothing selected...nothing to do
1357 if (line == wxNOT_FOUND)
1358 {
1359 // Allow further processing
1360 event.Skip();
1361 break;
1362 }
1363
1364 KeyNode *node = mLines[line];
1365
1366 // Only want parent nodes
1367 if (node->isparent)
1368 {
1369 // It is open so move select to first child
1370 if (node->isopen)
1371 {
1372 // But only if there is one
1373 if (line < (int) mLines.GetCount() - 1)
1374 {
1375 SelectNode(LineToIndex(line + 1));
1376 }
1377 }
1378 else
1379 {
1380 // Node is now open
1381 node->isopen = true;
1382
1383 // Don't want the view to scroll vertically, so remember the current
1384 // top line.
1385 size_t topline = GetVisibleBegin();
1386
1387 // Refresh the view now that the number of lines have changed
1388 RefreshLines();
1389
1390 // Reset the original top line
1391 ScrollToLine(topline);
1392
1393 // And make sure current line is still selected
1394 SelectNode(LineToIndex(line));
1395 }
1396 }
1397
1398 // Further processing of the event is not wanted
1399 // (we didn't call event.Skip()
1400 }
1401 break;
1402
1403 // Move selection to next node whose 1st character matches
1404 // the keycode
1405 default:
1406 {
1407 int cnt = (int) mLines.GetCount();
1408 bool found = false;
1409
1410 // Search the entire list if none is currently selected
1411 if (line == wxNOT_FOUND)
1412 {
1413 line = cnt;
1414 }
1415 else
1416 {
1417 // Search from the node following the current one
1418 for (int i = line + 1; i < cnt; i++)
1419 {
1420 wxString label;
1421
1422 // Get the string to search based on view type
1423 if (mViewType == ViewByTree)
1424 {
1425 label = GetLabel(LineToIndex(i));
1426 }
1427 else if (mViewType == ViewByName)
1428 {
1429 label = GetFullLabel(LineToIndex(i));
1430 }
1431 else if (mViewType == ViewByKey)
1432 {
1433 label = GetKey(LineToIndex(i));
1434 }
1435
1436 // Move selection if they match
1437 if (label.Left(1).IsSameAs(keycode, false))
1438 {
1439 SelectNode(LineToIndex(i));
1440
1441 found = true;
1442
1443 break;
1444 }
1445 }
1446 }
1447
1448 // A match wasn't found
1449 if (!found)
1450 {
1451 // So scan from the start of the list to the current node
1452 for (int i = 0; i < line; i++)
1453 {
1454 wxString label;
1455
1456 // Get the string to search based on view type
1457 if (mViewType == ViewByTree)
1458 {
1459 label = GetLabel(LineToIndex(i));
1460 }
1461 else if (mViewType == ViewByName)
1462 {
1463 label = GetFullLabel(LineToIndex(i));
1464 }
1465 else if (mViewType == ViewByKey)
1466 {
1467 label = GetKey(LineToIndex(i));
1468 }
1469
1470 // Move selection if they match
1471 if (label.Left(1).IsSameAs(keycode, false))
1472 {
1473 SelectNode(LineToIndex(i));
1474
1475 found = true;
1476
1477 break;
1478 }
1479 }
1480 }
1481
1482 // A node wasn't found so allow further processing
1483 if (!found) {
1484 event.Skip();
1485 }
1486
1487 // Otherwise, further processing of the event is not wanted
1488 // (we didn't call event.Skip()
1489 }
1490 }
1491 }
1492
1493 //
1494 // Handle the wxEVT_LEFT_DOWN event
1495 //
1496 void
1497 KeyView::OnLeftDown(wxMouseEvent & event)
1498 {
1499 // Only check if for tree view
1500 if (mViewType != ViewByTree)
1501 {
1502 // Allow further processing (important for focus handling)
1503 event.Skip();
1504
1505 return;
1506 }
1507
1508 // Get the mouse position when the button was pressed
1509 wxPoint pos = event.GetPosition();
1510
1511 // And see if it was on a line within the view
1512 int line = HitTest(pos);
1513
1514 // It was on a line
1515 if (line != wxNOT_FOUND)
1516 {
1517 KeyNode *node = mLines[line];
1518
1519 // Toggle the open state if this is a parent node
1520 if (node->isparent)
1521 {
1522 // Toggle state
1523 node->isopen = !node->isopen;
1524
1525 // Don't want the view to scroll vertically, so remember the current
1526 // top line.
1527 size_t topline = GetVisibleBegin();
1528
1529 // Refresh the view now that the number of lines have changed
1530 RefreshLines();
1531
1532 // Reset the original top line
1533 ScrollToLine(topline);
1534
1535 // And make sure current line is still selected
1536 SelectNode(LineToIndex(line));
1537 }
1538 }
1539
1540 // Allow further processing (important for focus handling)
1541 event.Skip();
1542 }
1543
1544 //
1545 // Sort compare function for tree view
1546 //
1547 // We want to leave the "menu" nodes alone as they are in the
1548 // order as they appear in the menus. But, we want to sort the
1549 // "command" nodes.
1550 //
1551 // To accomplish this, we prepend each label with it's line number
1552 // (in hex) for "menu" nodes. This ensures they will remain in
1553 // their original order.
1554 //
1555 // We prefix all "command" nodes with "ffffffff" (highest hex value)
1556 // to allow the sort to reorder them as needed.
1557 //
1558 int
1559 KeyView::CmpKeyNodeByTree(KeyNode ***n1, KeyNode ***n2)
1560 {
1561 KeyNode *t1 = (**n1);
1562 KeyNode *t2 = (**n2);
1563 wxString k1 = t1->label;
1564 wxString k2 = t2->label;
1565
1566 // This is a "command" node if its category is "Command"
1567 // and it is a child of the "Command" category. This latter
1568 // test ensures that the "Command" parent will be handled
1569 // as a "menu" node and remain at the bottom of the list.
1570 if (t1->category == _("Command") && !t1->isparent)
1571 {
1572 // A "command" node, so prepend the highest hex value
1573 k1.Printf(wxT("ffffffff%s"), t1->label.c_str());
1574 }
1575 else
1576 {
1577 // A "menu" node, so prepend the line number
1578 k1.Printf(wxT("%08x%s"), t1->line, t1->label.c_str());
1579 }
1580
1581 // See above for explanation
1582 if (t2->category == _("Command") && !t2->isparent)
1583 {
1584 // A "command" node, so prepend the highest hex value
1585 k2.Printf(wxT("ffffffff%s"), t2->label.c_str());
1586 }
1587 else
1588 {
1589 // A "menu" node, so prepend the line number
1590 k2.Printf(wxT("%08x%s"), t2->line, t2->label.c_str());
1591 }
1592
1593 // See wxWidgets documentation for explanation of comparison results.
1594 // These will produce an ascending order.
1595 if (k1 < k2)
1596 {
1597 return -1;
1598 }
1599
1600 if (k1 > k2)
1601 {
1602 return 1;
1603 }
1604
1605 return 0;
1606 }
1607
1608 //
1609 // Sort compare function for command view
1610 //
1611 // Nothing special here, just a standard ascending sort.
1612 //
1613 int
1614 KeyView::CmpKeyNodeByName(KeyNode ***n1, KeyNode ***n2)
1615 {
1616 KeyNode *t1 = (**n1);
1617 KeyNode *t2 = (**n2);
1618 wxString k1 = t1->label;
1619 wxString k2 = t2->label;
1620
1621 // Prepend prefix if available
1622 if (!t1->prefix.IsEmpty())
1623 {
1624 k1 = t1->prefix + wxT(" - ") + k1;
1625 }
1626
1627 // Prepend prefix if available
1628 if (!t2->prefix.IsEmpty())
1629 {
1630 k2 = t2->prefix + wxT(" - ") + k2;
1631 }
1632
1633 // See wxWidgets documentation for explanation of comparison results.
1634 // These will produce an ascending order.
1635 if (k1 < k2)
1636 {
1637 return -1;
1638 }
1639
1640 if (k1 > k2)
1641 {
1642 return 1;
1643 }
1644
1645 return 0;
1646 }
1647
1648 //
1649 // Sort compare function for key view
1650 //
1651 // We want all nodes with key assignments to appear in ascending order
1652 // at the top of the list and all nodes without assignment to appear in
1653 // ascending order at the bottom of the list.
1654 //
1655 // We accomplish this by by prefixing all non-assigned entries with 0xff.
1656 // This will force them to the end, but still allow them to be sorted in
1657 // ascending order.
1658 //
1659 // The assigned entries simply get sorted as normal.
1660 //
1661 int
1662 KeyView::CmpKeyNodeByKey(KeyNode ***n1, KeyNode ***n2)
1663 {
1664 KeyNode *t1 = (**n1);
1665 KeyNode *t2 = (**n2);
1666 wxString k1 = t1->key;
1667 wxString k2 = t2->key;
1668
1669 // Left node is unassigned, so prefix it
1670 if(k1.IsEmpty())
1671 {
1672 k1 = wxT("\xff");
1673 }
1674
1675 // Right node is unassigned, so prefix it
1676 if(k2.IsEmpty())
1677 {
1678 k2 = wxT("\xff");
1679 }
1680
1681 // Add prefix if available
1682 if (!t1->prefix.IsEmpty())
1683 {
1684 k1 += t1->prefix + wxT(" - ");
1685 }
1686
1687 // Add prefix if available
1688 if (!t2->prefix.IsEmpty())
1689 {
1690 k2 += t2->prefix + wxT(" - ");
1691 }
1692
1693 // Add labels
1694 k1 += t1->label;
1695 k2 += t2->label;
1696
1697 // See wxWidgets documentation for explanation of comparison results.
1698 // These will produce an ascending order.
1699 if (k1 < k2)
1700 {
1701 return -1;
1702 }
1703
1704 if (k1 > k2)
1705 {
1706 return 1;
1707 }
1708
1709 return 0;
1710 }
1711
1712 #if wxUSE_ACCESSIBILITY
1713
1714 //
1715 // Return parenthood state of line
1716 //
1717 bool
1718 KeyView::HasChildren(int line)
1719 {
1720 // Make sure line is valid
1721 if (line < 0 || line >= (int) mLines.GetCount())
1722 {
1723 wxASSERT(false);
1724 return false;
1725 }
1726
1727 return mLines[line]->isparent;
1728 }
1729
1730 //
1731 // Returns espanded/collapsed state of line
1732 //
1733 bool
1734 KeyView::IsExpanded(int line)
1735 {
1736 // Make sure line is valid
1737 if (line < 0 || line >= (int) mLines.GetCount())
1738 {
1739 wxASSERT(false);
1740 return false;
1741 }
1742
1743 return mLines[line]->isopen;
1744 }
1745
1746 //
1747 // Returns the height of the line
1748 //
1749 wxCoord
1750 KeyView::GetLineHeight(int line)
1751 {
1752 // Make sure line is valid
1753 if (line < 0 || line >= (int) mLines.GetCount())
1754 {
1755 wxASSERT(false);
1756 return 0;
1757 }
1758
1759 return OnGetLineHeight(line);
1760 }
1761
1762 //
1763 // Returns the value to be presented to accessibility
1764 //
1765 // Currently, the command and key are both provided.
1766 //
1767 wxString
1768 KeyView::GetValue(int line)
1769 {
1770 // Make sure line is valid
1771 if (line < 0 || line >= (int) mLines.GetCount())
1772 {
1773 wxASSERT(false);
1774 return wxEmptyString;
1775 }
1776 int index = LineToIndex(line);
1777
1778 // Get the label and key values
1779 wxString value;
1780 if (mViewType == ViewByTree)
1781 {
1782 value = GetLabel(index);
1783 }
1784 else
1785 {
1786 value = GetFullLabel(index);
1787 }
1788 wxString key = GetKey(index);
1789
1790 // Add the key if it isn't empty
1791 if (!key.IsEmpty())
1792 {
1793 if (mViewType == ViewByKey)
1794 {
1795 value = key + wxT(" ") + value;
1796 }
1797 else
1798 {
1799 value = value + wxT(" ") + key;
1800 }
1801 }
1802
1803 return value;
1804 }
1805
1806 //
1807 // Returns the current view type
1808 //
1809 ViewByType
1810 KeyView::GetViewType()
1811 {
1812 return mViewType;
1813 }
1814
1815 // ============================================================================
1816 // Accessibility provider for the KeyView class
1817 // ============================================================================
1818 KeyViewAx::KeyViewAx(KeyView *view)
1819 : wxWindowAccessible(view)
1820 {
1821 mView = view;
1822 mLastId = -1;
1823 }
1824
1825 //
1826 // Send an event notification to accessibility that the view
1827 // has changed.
1828 //
1829 void
1830 KeyViewAx::ListUpdated()
1831 {
1832 NotifyEvent(wxACC_EVENT_OBJECT_REORDER,
1833 mView,
1834 wxOBJID_CLIENT,
1835 0);
1836 }
1837
1838 //
1839 // Inform accessibility a new line has been selected and/or a previously
1840 // selected line is being unselected
1841 //
1842 void
1843 KeyViewAx::SetCurrentLine(int line)
1844 {
1845 // Only send selection remove notification if a line was
1846 // previously selected
1847 if (mLastId != -1)
1848 {
1849 NotifyEvent(wxACC_EVENT_OBJECT_SELECTIONREMOVE,
1850 mView,
1851 wxOBJID_CLIENT,
1852 mLastId);
1853 }
1854
1855 // Nothing is selected now
1856 mLastId = -1;
1857
1858 // Just clearing selection
1859 if (line != wxNOT_FOUND)
1860 {
1861 // Convert line number to childId
1862 LineToId(line, mLastId);
1863
1864 // Send notifications that the line has focus
1865 NotifyEvent(wxACC_EVENT_OBJECT_FOCUS,
1866 mView,
1867 wxOBJID_CLIENT,
1868 mLastId);
1869
1870 // And is selected
1871 NotifyEvent(wxACC_EVENT_OBJECT_SELECTION,
1872 mView,
1873 wxOBJID_CLIENT,
1874 mLastId);
1875 }
1876 }
1877
1878 //
1879 // Convert the childId to a line number and return FALSE if it
1880 // represents a child or TRUE if it a line
1881 //
1882 bool
1883 KeyViewAx::IdToLine(int childId, int & line)
1884 {
1885 if (childId == wxACC_SELF)
1886 {
1887 return false;
1888 }
1889
1890 // Convert to line
1891 line = childId - 1;
1892
1893 // Make sure id is valid
1894 if (line < 0 || line >= (int) mView->GetLineCount())
1895 {
1896 // Indicate the control itself in this case
1897 return false;
1898 }
1899
1900 return true;
1901 }
1902
1903 //
1904 // Convert the line number to a childId.
1905 //
1906 bool
1907 KeyViewAx::LineToId(int line, int & childId)
1908 {
1909 // Make sure line is valid
1910 if (line < 0 || line >= (int) mView->GetLineCount())
1911 {
1912 // Indicate the control itself in this case
1913 childId = wxACC_SELF;
1914 return false;
1915 }
1916
1917 // Convert to line
1918 childId = line + 1;
1919
1920 return true;
1921 }
1922
1923 // Can return either a child object, or an integer
1924 // representing the child element, starting from 1.
1925 wxAccStatus
1926 KeyViewAx::HitTest(const wxPoint & pt, int *childId, wxAccessible **childObject)
1927 {
1928 // Just to be safe
1929 *childObject = NULL;
1930
1931 wxPoint pos = mView->ScreenToClient(pt);
1932
1933 // See if it's on a line within the view
1934 int line = mView->HitTest(pos);
1935
1936 // It was on a line
1937 if (line != wxNOT_FOUND)
1938 {
1939 LineToId(line, *childId);
1940 return wxACC_OK;
1941 }
1942
1943 // Let the base class handle it
1944 return wxACC_NOT_IMPLEMENTED;
1945 }
1946
1947 // Retrieves the address of an IDispatch interface for the specified child.
1948 // All objects must support this property.
1949 wxAccStatus
1950 KeyViewAx::GetChild(int childId, wxAccessible** child)
1951 {
1952 if (childId == wxACC_SELF)
1953 {
1954 *child = this;
1955 }
1956 else
1957 {
1958 *child = NULL;
1959 }
1960
1961 return wxACC_OK;
1962 }
1963
1964 // Gets the number of children.
1965 wxAccStatus
1966 KeyViewAx::GetChildCount(int *childCount)
1967 {
1968 *childCount = (int) mView->GetLineCount();
1969
1970 return wxACC_OK;
1971 }
1972
1973 // Gets the default action for this object (0) or > 0 (the action for a child).
1974 // Return wxACC_OK even if there is no action. actionName is the action, or the empty
1975 // string if there is no action.
1976 // The retrieved string describes the action that is performed on an object,
1977 // not what the object does as a result. For example, a toolbar button that prints
1978 // a document has a default action of "Press" rather than "Prints the current document."
1979 wxAccStatus
1980 KeyViewAx::GetDefaultAction(int WXUNUSED(childId), wxString *actionName)
1981 {
1982 actionName->Clear();
1983
1984 return wxACC_OK;
1985 }
1986
1987 // Returns the description for this object or a child.
1988 wxAccStatus
1989 KeyViewAx::GetDescription(int WXUNUSED(childId), wxString *description)
1990 {
1991 description->Clear();
1992
1993 return wxACC_OK;
1994 }
1995
1996 // Returns help text for this object or a child, similar to tooltip text.
1997 wxAccStatus
1998 KeyViewAx::GetHelpText(int WXUNUSED(childId), wxString *helpText)
1999 {
2000 helpText->Clear();
2001
2002 return wxACC_OK;
2003 }
2004
2005 // Returns the keyboard shortcut for this object or child.
2006 // Return e.g. ALT+K
2007 wxAccStatus
2008 KeyViewAx::GetKeyboardShortcut(int WXUNUSED(childId), wxString *shortcut)
2009 {
2010 shortcut->Clear();
2011
2012 return wxACC_OK;
2013 }
2014
2015 // Returns the rectangle for this object (id = 0) or a child element (id > 0).
2016 // rect is in screen coordinates.
2017 wxAccStatus
2018 KeyViewAx::GetLocation(wxRect & rect, int elementId)
2019 {
2020 int line;
2021
2022 if (IdToLine(elementId, line))
2023 {
2024 if (!mView->IsVisible(line))
2025 {
2026 return wxACC_FAIL;
2027 }
2028
2029 wxRect rectLine;
2030
2031 rectLine.width = mView->GetClientSize().GetWidth();
2032
2033 // iterate over all visible lines
2034 for (int i = (int) mView->GetVisibleBegin(); i <= line; i++)
2035 {
2036 wxCoord hLine = mView->GetLineHeight(i);
2037
2038 rectLine.height = hLine;
2039
2040 rect = rectLine;
2041 wxPoint margins = mView->GetMargins();
2042 rect.Deflate(margins.x, margins.y);
2043 rectLine.y += hLine;
2044 }
2045
2046 rect.SetPosition(mView->ClientToScreen(rect.GetPosition()));
2047 }
2048 else
2049 {
2050 rect = mView->GetRect();
2051 rect.SetPosition(mView->GetParent()->ClientToScreen(rect.GetPosition()));
2052 }
2053
2054 return wxACC_OK;
2055 }
2056
2057 wxAccStatus
2058 KeyViewAx::Navigate(wxNavDir WXUNUSED(navDir),
2059 int WXUNUSED(fromId),
2060 int *WXUNUSED(toId),
2061 wxAccessible **WXUNUSED(toObject))
2062 {
2063 return wxACC_NOT_IMPLEMENTED;
2064 }
2065
2066 // Gets the name of the specified object.
2067 wxAccStatus
2068 KeyViewAx::GetName(int childId, wxString *name)
2069 {
2070 int line;
2071
2072 if (!IdToLine(childId, line))
2073 {
2074 *name = mView->GetName();
2075 }
2076 else
2077 {
2078 if (IdToLine(childId, line))
2079 {
2080 *name = mView->GetValue(line);
2081 }
2082 }
2083
2084 return wxACC_OK;
2085 }
2086
2087 wxAccStatus
2088 KeyViewAx::GetParent(wxAccessible ** WXUNUSED(parent))
2089 {
2090 return wxACC_NOT_IMPLEMENTED;
2091 }
2092
2093 // Returns a role constant.
2094 wxAccStatus
2095 KeyViewAx::GetRole(int childId, wxAccRole *role)
2096 {
2097 if (childId == wxACC_SELF)
2098 {
2099 #if defined(__WXMSW__)
2100 *role = mView->GetViewType() == ViewByTree ? wxROLE_SYSTEM_OUTLINE : wxROLE_SYSTEM_LIST;
2101 #endif
2102
2103 #if defined(__WXMAC__)
2104 *role = wxROLE_SYSTEM_GROUPING;
2105 #endif
2106 }
2107 else
2108 {
2109 #if defined(__WXMAC__)
2110 *role = wxROLE_SYSTEM_TEXT;
2111 #else
2112 *role = mView->GetViewType() == ViewByTree ? wxROLE_SYSTEM_OUTLINEITEM : wxROLE_SYSTEM_LISTITEM;
2113 #endif
2114 }
2115
2116 return wxACC_OK;
2117 }
2118
2119 // Gets a variant representing the selected children
2120 // of this object.
2121 // Acceptable values:
2122 // - a null variant (IsNull() returns TRUE)
2123 // - a list variant (GetType() == wxT("list"))
2124 // - an integer representing the selected child element,
2125 // or 0 if this object is selected (GetType() == wxT("long"))
2126 // - a "void*" pointer to a wxAccessible child object
2127 wxAccStatus
2128 KeyViewAx::GetSelections(wxVariant *selections)
2129 {
2130 int id;
2131
2132 LineToId(mView->GetSelection(), id);
2133
2134 *selections = (long) id;
2135
2136 return wxACC_OK;
2137 }
2138
2139 // Returns a state constant.
2140 wxAccStatus
2141 KeyViewAx::GetState(int childId, long *state)
2142 {
2143 int flag = wxACC_STATE_SYSTEM_FOCUSABLE;
2144 int line;
2145
2146 if (!IdToLine(childId, line))
2147 {
2148 *state = wxACC_STATE_SYSTEM_FOCUSABLE; // |
2149 //mView->FindFocus() == mView ? wxACC_STATE_SYSTEM_FOCUSED : 0;
2150 return wxACC_OK;
2151 }
2152
2153 #if defined(__WXMSW__)
2154 int selected = mView->GetSelection();
2155
2156 flag |= wxACC_STATE_SYSTEM_SELECTABLE;
2157
2158 if (line == selected)
2159 {
2160 flag |= wxACC_STATE_SYSTEM_FOCUSED |
2161 wxACC_STATE_SYSTEM_SELECTED;
2162 }
2163
2164 if (mView->HasChildren(line))
2165 {
2166 flag |= mView->IsExpanded(line) ?
2167 wxACC_STATE_SYSTEM_EXPANDED :
2168 wxACC_STATE_SYSTEM_COLLAPSED;
2169 }
2170 #endif
2171
2172 #if defined(__WXMAC__1)
2173 if (mGrid->IsInSelection(row, col))
2174 {
2175 flag |= wxACC_STATE_SYSTEM_SELECTED;
2176 }
2177
2178 if (mGrid->GetGridCursorRow() == row && mGrid->GetGridCursorCol() == col)
2179 {
2180 flag |= wxACC_STATE_SYSTEM_FOCUSED;
2181 }
2182
2183 if (mGrid->IsReadOnly(row, col))
2184 {
2185 flag |= wxACC_STATE_SYSTEM_UNAVAILABLE;
2186 }
2187 #endif
2188
2189 *state = flag;
2190
2191 return wxACC_OK;
2192 }
2193
2194 // Returns a localized string representing the value for the object
2195 // or child.
2196 wxAccStatus
2197 KeyViewAx::GetValue(int childId, wxString *strValue)
2198 {
2199 int line;
2200
2201 strValue->Clear();
2202
2203 if (!IdToLine(childId, line))
2204 {
2205 return wxACC_NOT_IMPLEMENTED;
2206 }
2207
2208 #if defined(__WXMSW__)
2209 if (mView->GetViewType() == ViewByTree)
2210 {
2211 KeyNode *node = mView->mLines[line];
2212 strValue->Printf(wxT("%d"), node->depth - 1);
2213 }
2214
2215 // Don't set a value for the other view types
2216 return wxACC_NOT_IMPLEMENTED;
2217 #endif
2218
2219 #if defined(__WXMAC__)
2220 return GetName(childId, strValue);
2221 #endif
2222 }
2223
2224 #if defined(__WXMAC__)
2225 // Selects the object or child.
2226 wxAccStatus
2227 KeyViewAx::Select(int childId, wxAccSelectionFlags selectFlags)
2228 {
2229 #if 0
2230 int row;
2231 int col;
2232
2233 if (GetRowCol(childId, row, col))
2234 {
2235
2236 if (selectFlags & wxACC_SEL_TAKESELECTION)
2237 {
2238 mGrid->SetGridCursor(row, col);
2239 }
2240
2241 mGrid->SelectBlock(row, col, row, col, selectFlags & wxACC_SEL_ADDSELECTION);
2242 }
2243 #endif
2244 return wxACC_OK;
2245 }
2246 #endif
2247
2248 // Gets the window with the keyboard focus.
2249 // If childId is 0 and child is NULL, no object in
2250 // this subhierarchy has the focus.
2251 // If this object has the focus, child should be 'this'.
2252 wxAccStatus
2253 KeyViewAx::GetFocus(int * WXUNUSED(childId), wxAccessible **child)
2254 {
2255 *child = this;
2256
2257 return wxACC_OK;
2258 }
2259
2260 #endif // wxUSE_ACCESSIBILITY
0 /**********************************************************************
1
2 Audacity: A Digital Audio Editor
3
4 KeyView.cpp
5
6 *******************************************************************//*!
7
8 \class KeyView
9 \brief Provides multiple views of keyboard shortcuts
10
11 *//*********************************************************************/
12
13 #include "../Audacity.h"
14
15 #include <wx/defs.h>
16 #include <wx/settings.h>
17 #include <wx/vlbox.h>
18
19 #include "../AColor.h"
20 #include "../ShuttleGui.h"
21 #include "../commands/CommandManager.h"
22 #include "../commands/Keyboard.h"
23 #include "KeyView.h"
24
25 #include <wx/arrimpl.cpp>
26
27 // Various drawing constants
28 #define KV_BITMAP_SIZE 16
29 #define KV_LEFT_MARGIN 2
30 #define KV_COLUMN_SPACER 5
31 #define KV_VSCROLL_WIDTH 16 /* figure this out automatically? */
32
33 // Define the KeyNode arrays
34 WX_DEFINE_OBJARRAY(KeyNodeArray);
35 WX_DEFINE_OBJARRAY(KeyNodeArrayPtr);
36
37 // Define the event table
38 BEGIN_EVENT_TABLE(KeyView, wxVListBox)
39 EVT_LEFT_DOWN(KeyView::OnLeftDown)
40 EVT_KEY_DOWN(KeyView::OnKeyDown)
41 EVT_LISTBOX(wxID_ANY, KeyView::OnSelected)
42 EVT_SET_FOCUS(KeyView::OnSetFocus)
43 EVT_KILL_FOCUS(KeyView::OnKillFocus)
44 EVT_SIZE(KeyView::OnSize)
45 EVT_SCROLLWIN(KeyView::OnScroll)
46 END_EVENT_TABLE();
47
48 // ============================================================================
49 // KeyView class
50 // ============================================================================
51 KeyView::KeyView(wxWindow *parent,
52 wxWindowID id,
53 const wxPoint & pos,
54 const wxSize & size)
55 : wxVListBox(parent, id, pos, size, wxBORDER_THEME),
56 mOpen(NULL),
57 mClosed(NULL),
58 mScrollX(0),
59 mWidth(0)
60 {
61 #if wxUSE_ACCESSIBILITY
62 // Create and set accessibility object
63 mAx = new KeyViewAx(this);
64 SetAccessible(mAx);
65 #endif
66
67 // Create the device context
68 wxMemoryDC dc;
69
70 // Create the open/expanded bitmap
71 mOpen = new wxBitmap(16, 16);
72 dc.SelectObject(*mOpen);
73
74 dc.SetBrush(*wxWHITE_BRUSH);
75 dc.SetPen(*wxWHITE_PEN);
76 dc.DrawRectangle(0, 0, 16, 16);
77
78 dc.SetPen(*wxBLACK_PEN);
79 dc.DrawRectangle(3, 4, 9, 9);
80 dc.DrawLine(5, 8, 10, 8);
81 dc.SelectObject(wxNullBitmap);
82
83 // Create the closed/collapsed bitmap
84 mClosed = new wxBitmap(16, 16);
85 dc.SelectObject(*mClosed);
86
87 dc.SetBrush(*wxWHITE_BRUSH);
88 dc.SetPen(*wxWHITE_PEN);
89 dc.DrawRectangle(0, 0, 16, 16);
90
91 dc.SetPen(*wxBLACK_PEN);
92 dc.DrawRectangle(3, 4, 9, 9);
93 dc.DrawLine(7, 6, 7, 11);
94 dc.DrawLine(5, 8, 10, 8);
95 dc.SelectObject(wxNullBitmap);
96
97 // The default view
98 mViewType = ViewByTree;
99 }
100
101 KeyView::~KeyView()
102 {
103 // Cleanup
104 if (mOpen)
105 {
106 delete mOpen;
107 }
108
109 if (mClosed)
110 {
111 delete mClosed;
112 }
113 }
114
115 //
116 // Returns the index of the selected node
117 //
118 int
119 KeyView::GetSelected() const
120 {
121 return LineToIndex(GetSelection());
122 }
123
124 //
125 // Returns the name of the control
126 //
127 wxString
128 KeyView::GetName() const
129 {
130 // Just forward request
131 return wxVListBox::GetName();
132 }
133
134 //
135 // Returns the label for the given index
136 //
137 wxString
138 KeyView::GetLabel(int index) const
139 {
140 // Make sure index is valid
141 if (index < 0 || index >= (int) mNodes.GetCount())
142 {
143 wxASSERT(false);
144 return wxEmptyString;
145 }
146
147 return mNodes[index].label;
148 }
149
150 //
151 // Returns the prefix (if available) prepended to the label for the given index
152 //
153 wxString
154 KeyView::GetFullLabel(int index) const
155 {
156 // Make sure index is valid
157 if (index < 0 || index >= (int) mNodes.GetCount())
158 {
159 wxASSERT(false);
160 return wxEmptyString;
161 }
162
163 // Cache the node and label
164 KeyNode & node = mNodes[index];
165 wxString label = node.label;
166
167 // Prepend the prefix if available
168 if (!node.prefix.IsEmpty())
169 {
170 label = node.prefix + wxT(" - ") + label;
171 }
172
173 return label;
174 }
175
176 //
177 // Returns the index for the given name
178 //
179 int
180 KeyView::GetIndexByName(const wxString & name) const
181 {
182 int cnt = (int) mNodes.GetCount();
183
184 // Search the nodes for the key
185 for (int i = 0; i < cnt; i++)
186 {
187 if (name.CmpNoCase(mNodes[i].name) == 0)
188 {
189 return mNodes[i].index;
190 }
191 }
192
193 return wxNOT_FOUND;
194 }
195
196 //
197 // Returns the command manager name for the given index
198 //
199 wxString
200 KeyView::GetName(int index) const
201 {
202 // Make sure index is valid
203 if (index < 0 || index >= (int) mNodes.GetCount())
204 {
205 wxASSERT(false);
206 return wxEmptyString;
207 }
208
209 return mNodes[index].name;
210 }
211
212 //
213 // Returns the command manager index for the given key combination
214 //
215 wxString
216 KeyView::GetNameByKey(const wxString & key) const
217 {
218 int cnt = (int) mNodes.GetCount();
219
220 // Search the nodes for the key
221 for (int i = 0; i < cnt; i++)
222 {
223 if (key.CmpNoCase(mNodes[i].key) == 0)
224 {
225 return mNodes[i].name;
226 }
227 }
228
229 return wxEmptyString;
230 }
231
232 //
233 // Returns the index for the given key
234 //
235 int
236 KeyView::GetIndexByKey(const wxString & key) const
237 {
238 int cnt = (int) mNodes.GetCount();
239
240 // Search the nodes for the key
241 for (int i = 0; i < cnt; i++)
242 {
243 if (key.CmpNoCase(mNodes[i].key) == 0)
244 {
245 return mNodes[i].index;
246 }
247 }
248
249 return wxNOT_FOUND;
250 }
251
252 //
253 // Returns the key for the given index
254 //
255 wxString
256 KeyView::GetKey(int index) const
257 {
258 // Make sure index is valid
259 if (index < 0 || index >= (int) mNodes.GetCount())
260 {
261 wxASSERT(false);
262 return wxEmptyString;
263 }
264
265 return mNodes[index].key;
266 }
267
268 //
269 // Use to determine if a key can be assigned to the given index
270 //
271 bool
272 KeyView::CanSetKey(int index) const
273 {
274 // Make sure index is valid
275 if (index < 0 || index >= (int) mNodes.GetCount())
276 {
277 wxASSERT(false);
278 return false;
279 }
280
281 // Parents can't be assigned keys
282 return !mNodes[index].isparent;
283 }
284
285 //
286 // Sets the key for the given index
287 //
288 bool
289 KeyView::SetKey(int index, const wxString & key)
290 {
291 // Make sure index is valid
292 if (index < 0 || index >= (int) mNodes.GetCount())
293 {
294 wxASSERT(false);
295 return false;
296 }
297
298 // Cache the node
299 KeyNode & node = mNodes[index];
300
301 // Do not allow setting keys on branches
302 if (node.isparent)
303 {
304 return false;
305 }
306
307 // Set the new key
308 node.key = key;
309
310 // Check to see if the key column needs to be expanded
311 int x, y;
312 GetTextExtent(node.key, &x, &y);
313 if (x > mKeyWidth || y > mLineHeight)
314 {
315 // New key is wider than column so recalc extents (will refresh view)
316 RecalcExtents();
317 return true;
318 }
319
320 // Refresh the view lines
321 RefreshAll();
322
323 return true;
324 }
325
326 //
327 // Sets the key for the given name
328 //
329 bool
330 KeyView::SetKeyByName(const wxString & name, const wxString & key)
331 {
332 int index = GetIndexByName(name);
333
334 // Bail is the name wasn't found
335 if (index == wxNOT_FOUND)
336 {
337 return false;
338 }
339
340 // Go set the key
341 return SetKey(index, key);
342 }
343
344 //
345 // Sets the view type
346 //
347 void
348 KeyView::SetView(ViewByType type)
349 {
350 int index = LineToIndex(GetSelection());
351
352 // Handle an existing selection
353 if (index != wxNOT_FOUND)
354 {
355 // Cache the currently selected node
356 KeyNode & node = mNodes[index];
357
358 // Expand branches if switching to Tree view and a line
359 // is currently selected
360 if (type == ViewByTree)
361 {
362 // Cache the node's depth
363 int depth = node.depth;
364
365 // Search for its parents, setting each one as open
366 for (int i = node.index - 1; i >= 0 && depth > 1; i--)
367 {
368 if (mNodes[i].depth < depth)
369 {
370 mNodes[i].isopen = true;
371 depth = mNodes[i].depth;
372 }
373 }
374 }
375 }
376
377 // Unselect any currently selected line...do even if none selected
378 SelectNode(-1);
379
380 // Save new type
381 mViewType = type;
382
383 // Refresh the view lines
384 RefreshLines();
385
386 // Reselect old node (if possible)
387 if (index != wxNOT_FOUND)
388 {
389 SelectNode(index);
390 }
391
392 return;
393 }
394
395 //
396 // Sets the filter
397 //
398 void
399 KeyView::SetFilter(const wxString & filter)
400 {
401 int index = LineToIndex(GetSelection());
402
403 // Unselect any currently selected line...do even if none selected
404 SelectNode(-1);
405
406 // Save the filter
407 mFilter = filter.Lower();
408
409 // Refresh the view lines
410 RefreshLines();
411
412 // Reselect old node (if possible)
413 if (index != wxNOT_FOUND)
414 {
415 SelectNode(index);
416 }
417 }
418
419 //
420 // Expand all branches
421 //
422 void
423 KeyView::ExpandAll()
424 {
425 int cnt = (int) mNodes.GetCount();
426
427 // Set all parent nodes to open
428 for (int i = 0; i < cnt; i++)
429 {
430 KeyNode & node = mNodes[i];
431
432 if (node.isparent)
433 {
434 node.isopen = true;
435 }
436 }
437
438 RefreshLines();
439 }
440
441 //
442 // Collapse all branches
443 //
444 void
445 KeyView::CollapseAll()
446 {
447 int cnt = (int) mNodes.GetCount();
448
449 // Set all parent nodes to closed
450 for (int i = 0; i < cnt; i++)
451 {
452 KeyNode & node = mNodes[i];
453
454 if (node.isparent)
455 {
456 node.isopen = false;
457 }
458 }
459
460 RefreshLines();
461 }
462
463 //
464 // Recalculate the measurements used for columns and scrolling
465 //
466 void
467 KeyView::RecalcExtents()
468 {
469 // Reset
470 mLineHeight = 0;
471 mCommandWidth = 0;
472 mKeyWidth = 0;
473
474 // Examine all nodes
475 int cnt = (int) mNodes.GetCount();
476 for (int i = 0; i < cnt; i++)
477 {
478 KeyNode & node = mNodes[i];
479 int x, y;
480
481 if (node.iscat)
482 {
483 // Measure the category
484 GetTextExtent(node.category, &x, &y);
485 }
486 else if (node.ispfx)
487 {
488 // Measure the prefix
489 GetTextExtent(node.prefix, &x, &y);
490 }
491 else
492 {
493 // Measure the key
494 GetTextExtent(node.key, &x, &y);
495 mLineHeight = wxMax(mLineHeight, y);
496 mKeyWidth = wxMax(mKeyWidth, x);
497
498 // Prepend prefix for view types other than tree
499 wxString label = node.label;
500 if (mViewType != ViewByTree && !node.prefix.IsEmpty())
501 {
502 label = node.prefix + wxT(" - ") + label;
503 }
504
505 // Measure the label
506 GetTextExtent(label, &x, &y);
507 }
508
509 // Finish calc for command column
510 mLineHeight = wxMax(mLineHeight, y);
511 mCommandWidth = wxMax(mCommandWidth, x);
512 }
513
514 // Update horizontal scrollbar
515 UpdateHScroll();
516 }
517
518 //
519 // Update the horizontal scrollbar or remove it if not needed
520 //
521 void
522 KeyView::UpdateHScroll()
523 {
524 // Get the internal dimensions of the view
525 wxRect r = GetClientRect();
526
527 // Calculate the full line width
528 mWidth = KV_LEFT_MARGIN +
529 mCommandWidth +
530 KV_COLUMN_SPACER +
531 mKeyWidth +
532 KV_VSCROLL_WIDTH;
533
534 // Retrieve the current horizontal scroll amount
535 mScrollX = GetScrollPos(wxHORIZONTAL);
536
537 if (mWidth <= r.GetWidth())
538 {
539 // Remove the scrollbar if it will fit within client width
540 SetScrollbar(wxHORIZONTAL, 0, 0, 0);
541 }
542 else
543 {
544 // Set scrollbar metrics
545 SetScrollbar(wxHORIZONTAL, mScrollX, r.GetWidth(), mWidth);
546 }
547
548 // Refresh the entire view
549 RefreshAll();
550 }
551
552 //
553 // Process a new set of bindings
554 //
555 void
556 KeyView::RefreshBindings(const wxArrayString & names,
557 const wxArrayString & categories,
558 const wxArrayString & prefixes,
559 const wxArrayString & labels,
560 const wxArrayString & keys)
561 {
562 bool firsttime = mNodes.GetCount() == 0;
563
564 // Start clean
565 mNodes.Clear();
566
567 // Same as in RecalcExtents() but do it inline
568 mLineHeight = 0;
569 mKeyWidth = 0;
570 mCommandWidth = 0;
571
572 wxString lastcat;
573 wxString lastpfx;
574 int nodecnt = 0;
575 int depth = 1;
576 bool incat = false;
577 bool inpfx = false;
578
579 // Examine all names...all arrays passed have the same indexes
580 int cnt = (int) names.GetCount();
581 for (int i = 0; i < cnt; i++)
582 {
583 wxString name = names[i];
584 int x, y;
585
586 // Remove any menu code from the category and prefix
587 wxString cat = wxMenuItem::GetLabelFromText(categories[i]);
588 wxString pfx = wxMenuItem::GetLabelFromText(prefixes[i]);
589
590 // Append "Menu" this node is for a menu title
591 if (cat != wxT("Command"))
592 {
593 cat.Append(wxT(" "));
594 cat += _("Menu");
595 }
596
597 // Process a new category
598 if (cat != lastcat)
599 {
600 // A new category always finishes any current subtree
601 if (inpfx)
602 {
603 // Back to category level
604 depth--;
605 inpfx = false;
606 }
607
608 // Only time this is not true is during the first iteration
609 if (incat)
610 {
611 // Back to root level
612 depth--;
613 incat = false;
614 }
615
616 // Remember for next iteration
617 lastcat = cat;
618
619 // Add a new category node
620 if (cat != wxEmptyString)
621 {
622 KeyNode node;
623
624 // Fill in the node info
625 node.name = wxEmptyString; // don't associate branches with a command
626 node.category = cat;
627 node.prefix = pfx;
628 node.label = cat;
629 node.index = nodecnt++;
630 node.iscat = true;
631 node.isparent = true;
632 node.depth = depth++;
633
634 // Add it to the tree
635 mNodes.Add(node);
636 incat = true;
637
638 // Measure category
639 GetTextExtent(cat, &x, &y);
640 mLineHeight = wxMax(mLineHeight, y);
641 mCommandWidth = wxMax(mCommandWidth, x);
642 }
643 }
644
645 // Process a new prefix
646 if (pfx != lastpfx)
647 {
648 // Done with prefix branch
649 if (inpfx)
650 {
651 depth--;
652 inpfx = false;
653 }
654
655 // Remember for next iteration
656 lastpfx = pfx;
657
658 // Add a new prefix node
659 if (pfx != wxEmptyString)
660 {
661 KeyNode node;
662
663 // Fill in the node info
664 node.name = wxEmptyString; // don't associate branches with a command
665 node.category = cat;
666 node.prefix = pfx;
667 node.label = pfx;
668 node.index = nodecnt++;
669 node.ispfx = true;
670 node.isparent = true;
671 node.depth = depth++;
672
673 // Add it to the tree
674 mNodes.Add(node);
675 inpfx = true;
676 }
677 }
678
679 // Add the key entry
680 KeyNode node;
681 node.category = cat;
682 node.prefix = pfx;
683
684 // Labels for undo and redo change according to the last command
685 // which can be undone/redone, so give them a special check in order
686 // not to confuse users
687 if (name == wxT("Undo"))
688 {
689 node.label = _("Undo");
690 }
691 else if (name == wxT("Redo"))
692 {
693 node.label = _("Redo");
694 }
695 else
696 {
697 // Strip any menu codes from label
698 node.label = wxMenuItem::GetLabelFromText(labels[i].BeforeFirst(wxT('\t')));
699 }
700
701 // Fill in remaining info
702 node.name = name;
703 node.key = KeyStringDisplay(keys[i]);
704 node.index = nodecnt++;
705 node.depth = depth;
706
707 // Add it to the tree
708 mNodes.Add(node);
709
710 // Measure key
711 GetTextExtent(node.key, &x, &y);
712 mLineHeight = wxMax(mLineHeight, y);
713 mKeyWidth = wxMax(mKeyWidth, x);
714
715 // Prepend prefix for all view types to determine maximum
716 // column widths
717 wxString label = node.label;
718 if (!node.prefix.IsEmpty())
719 {
720 label = node.prefix + wxT(" - ") + label;
721 }
722
723 // Measure label
724 GetTextExtent(label, &x, &y);
725 mLineHeight = wxMax(mLineHeight, y);
726 mCommandWidth = wxMax(mCommandWidth, x);
727 }
728
729 #if 0
730 // For debugging
731 for (int j = 0; j < mNodes.GetCount(); j++)
732 {
733 KeyNode & node = mNodes[j];
734 wxLogDebug(wxT("NODE line %4d index %4d depth %1d open %1d parent %1d cat %1d pfx %1d name %s STR %s | %s | %s"),
735 node.line,
736 node.index,
737 node.depth,
738 node.isopen,
739 node.isparent,
740 node.iscat,
741 node.ispfx,
742 node.name.c_str(),
743 node.category.c_str(),
744 node.prefix.c_str(),
745 node.label.c_str());
746 }
747 #endif
748
749 // Update horizontal scrollbar
750 UpdateHScroll();
751
752 // Refresh the view lines
753 RefreshLines();
754
755 // Set the selected node if this was the first time through
756 if (firsttime)
757 {
758 SelectNode(LineToIndex(0));
759 }
760 }
761
762 //
763 // Refresh the list of lines within the current view
764 //
765 void
766 KeyView::RefreshLines()
767 {
768 int cnt = (int) mNodes.GetCount();
769 int linecnt = 0;
770 mLines.Empty();
771
772 // Process a filter if one is set
773 if (!mFilter.IsEmpty())
774 {
775 // Examine all nodes
776 for (int i = 0; i < cnt; i++)
777 {
778 KeyNode & node = mNodes[i];
779
780 // Reset line number
781 node.line = wxNOT_FOUND;
782
783 // Search columns based on view type
784 wxString searchit;
785 switch (mViewType)
786 {
787 // The x"01" separator is used to prevent finding a
788 // match comprising the end of the label and beginning
789 // of the key. It was chosen since it's not very likely
790 // to appear in the filter itself.
791 case ViewByTree:
792 searchit = node.label.Lower() +
793 wxT("\01x") +
794 node.key.Lower();
795 break;
796
797 case ViewByName:
798 searchit = node.label.Lower();
799 break;
800
801 case ViewByKey:
802 searchit = node.key.Lower();
803 break;
804 }
805 if (searchit.Find(mFilter) == wxNOT_FOUND)
806 {
807 // Not found so continue to next node
808 continue;
809 }
810
811 // For the Key View, if the filter is a single character,
812 // then it has to be the last character in the searchit string,
813 // and be preceded by nothing or +.
814 if ((mViewType == ViewByKey) &&
815 (mFilter.Len() == 1) &&
816 (!mFilter.IsSameAs(searchit.Last()) ||
817 ((searchit.Len() > 1) &&
818 ((wxString)(searchit.GetChar(searchit.Len() - 2)) != wxT("+")))))
819 {
820 // Not suitable so continue to next node
821 continue;
822 }
823
824 // For tree view, we must make sure all parent nodes are included
825 // whether they match the filter or not.
826 if (mViewType == ViewByTree)
827 {
828 KeyNodeArrayPtr queue;
829 int depth = node.depth;
830
831 // This node is a category or prefix node, so always mark them
832 // as open.
833 //
834 // What this is really doing is resolving a situation where the
835 // the filter matches a parent node and nothing underneath. In
836 // this case, the node would never be marked as open.
837 if (node.isparent)
838 {
839 node.isopen = true;
840 }
841
842 // Examine siblings until a parent is found.
843 for (int j = node.index - 1; j >= 0 && depth > 0; j--)
844 {
845 // Found a parent
846 if (mNodes[j].depth < depth)
847 {
848 // Examine all previously added nodes to see if this nodes
849 // ancestors need to be added prior to adding this node.
850 bool found = false;
851 for (int k = (int) mLines.GetCount() - 1; k >= 0; k--)
852 {
853 // The node indexes match, so we've found the parent of the
854 // child node.
855 if (mLines[k]->index == mNodes[j].index)
856 {
857 found = true;
858 break;
859 }
860 }
861
862 // The parent wasn't found so remember it for later
863 // addition. Can't add directory to mLines here since
864 // they will wind up in reverse order.
865 if (!found)
866 {
867 queue.Add(&mNodes[j]);
868 }
869
870 // Traverse up the tree
871 depth = mNodes[j].depth;
872 }
873 }
874
875 // Add any queues nodes to list. This will all be
876 // parent nodes, so mark them as open.
877 for (int j = (int) queue.GetCount() - 1; j >= 0; j--)
878 {
879 queue[j]->isopen = true;
880 queue[j]->line = linecnt++;
881 mLines.Add(queue[j]);
882 }
883 }
884
885 // Finally add the child node
886 node.line = linecnt++;
887 mLines.Add(&node);
888 }
889 }
890 else
891 {
892 // Examine all nodes - non-filtered
893 for (int i = 0; i < cnt; i++)
894 {
895 KeyNode & node = mNodes[i];
896
897 // Reset line number
898 node.line = wxNOT_FOUND;
899
900 // Node is either a category or prefix
901 if (node.isparent)
902 {
903 // Only need to do this for tree views
904 if (mViewType != ViewByTree)
905 {
906 continue;
907 }
908
909 // Add the node
910 node.line = linecnt++;
911 mLines.Add(&node);
912
913 // If this node is not open, then skip all of it's decendants
914 if (!node.isopen)
915 {
916 bool iscat = node.iscat;
917 bool ispfx = node.ispfx;
918
919 // Skip nodes until we find a node that has a different
920 // category or prefix
921 while (i < cnt)
922 {
923 KeyNode & skip = mNodes[i];
924
925 if ((iscat && skip.category != node.category) ||
926 (ispfx && skip.prefix != node.prefix))
927 {
928 break;
929 }
930
931 // Bump to next node
932 i++;
933 }
934
935 // Index is pointing to the node that was different or
936 // past the end, so back off to last node of this branch.
937 i--;
938 }
939 continue;
940 }
941
942 // Add child node to list
943 node.line = linecnt++;
944 mLines.Add(&node);
945 }
946 }
947
948 // Sort list based on type
949 switch (mViewType)
950 {
951 case ViewByTree:
952 mLines.Sort(CmpKeyNodeByTree);
953 break;
954
955 case ViewByName:
956 mLines.Sort(CmpKeyNodeByName);
957 break;
958
959 case ViewByKey:
960 mLines.Sort(CmpKeyNodeByKey);
961 break;
962 }
963
964 // Now, reassign the line numbers
965 for (int i = 0; i < (int) mLines.GetCount(); i++)
966 {
967 mLines[i]->line = i;
968 }
969
970 #if 0
971 // For debugging
972 for (int j = 0; j < mLines.GetCount(); j++)
973 {
974 KeyNode & node = *mLines[j];
975 wxLogDebug(wxT("LINE line %4d index %4d depth %1d open %1d parent %1d cat %1d pfx %1d name %s STR %s | %s | %s"),
976 node.line,
977 node.index,
978 node.depth,
979 node.isopen,
980 node.isparent,
981 node.iscat,
982 node.ispfx,
983 node.name.c_str(),
984 node.category.c_str(),
985 node.prefix.c_str(),
986 node.label.c_str());
987 }
988 #endif
989
990 // Tell listbox the new count and refresh the entire view
991 SetItemCount(mLines.GetCount());
992 RefreshAll();
993
994 #if wxUSE_ACCESSIBILITY
995 // Let accessibility know that the list has changed
996 mAx->ListUpdated();
997 #endif
998 }
999
1000 //
1001 // Select a node
1002 //
1003 // Parameter can be wxNOT_FOUND to clear selection
1004 //
1005 void
1006 KeyView::SelectNode(int index)
1007 {
1008 int line = IndexToLine(index);
1009
1010 // Tell the listbox to select the line
1011 SetSelection(line);
1012
1013 #if wxUSE_ACCESSIBILITY
1014 // And accessibility
1015 mAx->SetCurrentLine(line);
1016 #endif
1017
1018 // Always send an event to let parent know of selection change
1019 //
1020 // Must do this ourselves becuase we want to send notifications
1021 // even if there isn't an item selected and SendSelectedEvent()
1022 // doesn't allow sending an event for indexes not in the listbox.
1023 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, GetId());
1024 event.SetEventObject(this);
1025 event.SetInt(line);
1026 (void)GetEventHandler()->ProcessEvent(event);
1027 }
1028
1029 //
1030 // Converts a line index to a node index
1031 //
1032 int
1033 KeyView::LineToIndex(int line) const
1034 {
1035 if (line < 0 || line >= (int) mLines.GetCount())
1036 {
1037 return wxNOT_FOUND;
1038 }
1039
1040 return mLines[line]->index;
1041 }
1042
1043 //
1044 // Converts a node index to a line index
1045 //
1046 int
1047 KeyView::IndexToLine(int index) const
1048 {
1049 if (index < 0 || index >= (int) mNodes.GetCount())
1050 {
1051 return wxNOT_FOUND;
1052 }
1053
1054 return mNodes[index].line;
1055 }
1056
1057 //
1058 // Draw the background for a given line
1059 //
1060 // This is called by the listbox when it needs to redraw the view.
1061 //
1062 void
1063 KeyView::OnDrawBackground(wxDC & dc, const wxRect & rect, size_t line) const
1064 {
1065 const KeyNode *node = mLines[line];
1066 wxRect r = rect;
1067 wxCoord indent = 0;
1068
1069 // When in tree view mode, each younger branch gets indented by the
1070 // width of the open/close bitmaps
1071 if (mViewType == ViewByTree)
1072 {
1073 indent += node->depth * KV_BITMAP_SIZE;
1074 }
1075
1076 // Offset left side by the indentation (if any) and scroll amounts
1077 r.x = indent - mScrollX;
1078
1079 // If the line width is less than the client width, then we want to
1080 // extend the background to the right edge of the client view. Otherwise,
1081 // go all the way to the end of the line width...this will draw past the
1082 // right edge, but that's what we want.
1083 r.width = wxMax(mWidth, r.width);
1084
1085 // Selected lines get a solid background
1086 if (IsSelected(line))
1087 {
1088 if (FindFocus() == this)
1089 {
1090 // Focused lines get highlighted background
1091 dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)));
1092 AColor::DrawFocus(dc, r);
1093 dc.DrawRectangle(r);
1094 }
1095 else
1096 {
1097 // Non ocused lines get a light background
1098 dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)));
1099 dc.SetPen(*wxTRANSPARENT_PEN);
1100 dc.DrawRectangle(r);
1101 }
1102 }
1103 else
1104 {
1105 // Non-selected lines get a thin bottom border
1106 dc.SetPen(wxColour(240, 240, 240));
1107 dc.DrawLine(r.GetLeft(), r.GetBottom(), r.GetRight(), r.GetBottom());
1108 }
1109 }
1110
1111 //
1112 // Draw a line
1113 //
1114 // This is called by the listbox when it needs to redraw the view.
1115 //
1116 void
1117 KeyView::OnDrawItem(wxDC & dc, const wxRect & rect, size_t line) const
1118 {
1119 const KeyNode *node = mLines[line];
1120 wxString label = node->label;
1121
1122 // Make sure the DC has a valid font
1123 dc.SetFont(GetFont());
1124
1125 // Set the text color based on selection and focus
1126 if (IsSelected(line) && FindFocus() == this)
1127 {
1128 dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT));
1129 }
1130 else
1131 {
1132 dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT));
1133 }
1134
1135 // Tree views get bitmaps
1136 if (mViewType == ViewByTree)
1137 {
1138 // Adjust left edge to account for scrolling
1139 wxCoord x = rect.x - mScrollX;
1140
1141 if (node->iscat)
1142 {
1143 // Draw categories bitmap at left edge
1144 dc.DrawBitmap(node->isopen ? *mOpen : *mClosed, x, rect.y);
1145 }
1146 else if (node->ispfx)
1147 {
1148 // Draw prefix bitmap to the right of the category bitmap
1149 dc.DrawBitmap(node->isopen ? *mOpen : *mClosed, x + KV_BITMAP_SIZE, rect.y);
1150 }
1151
1152 // Indent text
1153 x += KV_LEFT_MARGIN;
1154
1155 // Draw the command and key columns
1156 dc.DrawText(label, x + node->depth * KV_BITMAP_SIZE, rect.y);
1157 dc.DrawText(node->key, x + mCommandWidth + KV_COLUMN_SPACER, rect.y);
1158 }
1159 else
1160 {
1161 // Adjust left edge by margin and account for scrolling
1162 wxCoord x = rect.x + KV_LEFT_MARGIN - mScrollX;
1163
1164 // Prepend prefix if available
1165 if (!node->prefix.IsEmpty())
1166 {
1167 label = node->prefix + wxT(" - ") + label;
1168 }
1169
1170 // Swap the columns based on view type
1171 if(mViewType == ViewByName)
1172 {
1173 // Draw command column and then key column
1174 dc.DrawText(label, x, rect.y);
1175 dc.DrawText(node->key, x + mCommandWidth + KV_COLUMN_SPACER, rect.y);
1176 }
1177 else if(mViewType == ViewByKey)
1178 {
1179 // Draw key columnd and then command column
1180 dc.DrawText(node->key, x, rect.y);
1181 dc.DrawText(label, x + mKeyWidth + KV_COLUMN_SPACER, rect.y);
1182 }
1183 }
1184
1185 return;
1186 }
1187
1188 //
1189 // Provide the height of the given line
1190 //
1191 // This is called by the listbox when it needs to redraw the view.
1192 //
1193 wxCoord
1194 KeyView::OnMeasureItem(size_t WXUNUSED(line)) const
1195 {
1196 // All lines are of equal height
1197 //
1198 // (add a magic 1 for decenders...looks better...not required)
1199 return mLineHeight + 1;
1200 }
1201
1202 //
1203 // Handle the wxEVT_LISTBOX event
1204 //
1205 void
1206 KeyView::OnSelected(wxCommandEvent & event)
1207 {
1208 // Allow further processing
1209 event.Skip();
1210
1211 #if wxUSE_ACCESSIBILITY
1212 // Tell accessibility of the change
1213 mAx->SetCurrentLine(event.GetInt());
1214 #endif
1215 }
1216
1217 //
1218 // Handle the wxEVT_SET_FOCUS event
1219 //
1220 void
1221 KeyView::OnSetFocus(wxFocusEvent & event)
1222 {
1223 // Allow further processing
1224 event.Skip();
1225
1226 // Refresh the selected line to pull in any changes while
1227 // focus was away...like when setting a new key value. This
1228 // will also refresh the visual (highlighted) state.
1229 if (GetSelection() != wxNOT_FOUND)
1230 {
1231 RefreshLine(GetSelection());
1232 }
1233
1234 #if wxUSE_ACCESSIBILITY
1235 // Tell accessibility of the change
1236 mAx->SetCurrentLine(GetSelection());
1237 #endif
1238 }
1239
1240 //
1241 // Handle the wxEVT_KILL_FOCUS event
1242 //
1243 void
1244 KeyView::OnKillFocus(wxFocusEvent & event)
1245 {
1246 // Allow further processing
1247 event.Skip();
1248
1249 // Refresh the selected line to adjust visual highlighting.
1250 if (GetSelection() != wxNOT_FOUND)
1251 {
1252 RefreshLine(GetSelection());
1253 }
1254 }
1255
1256 //
1257 // Handle the wxEVT_SIZE event
1258 //
1259 void
1260 KeyView::OnSize(wxSizeEvent & WXUNUSED(event))
1261 {
1262 // Update horizontal scrollbar
1263 UpdateHScroll();
1264 }
1265
1266 //
1267 // Handle the wxEVT_SCROLL event
1268 //
1269 void
1270 KeyView::OnScroll(wxScrollWinEvent & event)
1271 {
1272 // We only care bout the horizontal scrollbar.
1273 if (event.GetOrientation() != wxHORIZONTAL)
1274 {
1275 // Allow further processing
1276 event.Skip();
1277 return;
1278 }
1279
1280 // Get new scroll position and scroll the view
1281 mScrollX = event.GetPosition();
1282 SetScrollPos(wxHORIZONTAL, mScrollX);
1283
1284 // Refresh the entire view
1285 RefreshAll();
1286 }
1287
1288 //
1289 // Handle the wxEVT_KEY_DOWN event
1290 //
1291 void
1292 KeyView::OnKeyDown(wxKeyEvent & event)
1293 {
1294 int line = GetSelection();
1295
1296 int keycode = event.GetKeyCode();
1297 switch (keycode)
1298 {
1299 // The LEFT key moves selection to parent or collapses selected
1300 // node if it is expanded.
1301 case WXK_LEFT:
1302 {
1303 // Nothing selected...nothing to do
1304 if (line == wxNOT_FOUND)
1305 {
1306 // Allow further processing
1307 event.Skip();
1308 break;
1309 }
1310
1311 KeyNode *node = mLines[line];
1312
1313 // Collapse the node if it is open
1314 if (node->isopen)
1315 {
1316 // No longer open
1317 node->isopen = false;
1318
1319 // Don't want the view to scroll vertically, so remember the current
1320 // top line.
1321 size_t topline = GetVisibleBegin();
1322
1323 // Refresh the view now that the number of lines have changed
1324 RefreshLines();
1325
1326 // Reset the original top line
1327 ScrollToLine(topline);
1328
1329 // And make sure current line is still selected
1330 SelectNode(LineToIndex(line));
1331 }
1332 else
1333 {
1334 // Move selection to the parent of this node
1335 for (int i = line - 1; i >= 0; i--)
1336 {
1337 // Found the parent
1338 if (mLines[i]->depth < node->depth)
1339 {
1340 // So select it
1341 SelectNode(LineToIndex(i));
1342 break;
1343 }
1344 }
1345 }
1346
1347 // Further processing of the event is not wanted
1348 // (we didn't call event.Skip()
1349 }
1350 break;
1351
1352 // The RIGHT key moves the selection to the first child or expands
1353 // the node if it is a parent.
1354 case WXK_RIGHT:
1355 {
1356 // Nothing selected...nothing to do
1357 if (line == wxNOT_FOUND)
1358 {
1359 // Allow further processing
1360 event.Skip();
1361 break;
1362 }
1363
1364 KeyNode *node = mLines[line];
1365
1366 // Only want parent nodes
1367 if (node->isparent)
1368 {
1369 // It is open so move select to first child
1370 if (node->isopen)
1371 {
1372 // But only if there is one
1373 if (line < (int) mLines.GetCount() - 1)
1374 {
1375 SelectNode(LineToIndex(line + 1));
1376 }
1377 }
1378 else
1379 {
1380 // Node is now open
1381 node->isopen = true;
1382
1383 // Don't want the view to scroll vertically, so remember the current
1384 // top line.
1385 size_t topline = GetVisibleBegin();
1386
1387 // Refresh the view now that the number of lines have changed
1388 RefreshLines();
1389
1390 // Reset the original top line
1391 ScrollToLine(topline);
1392
1393 // And make sure current line is still selected
1394 SelectNode(LineToIndex(line));
1395 }
1396 }
1397
1398 // Further processing of the event is not wanted
1399 // (we didn't call event.Skip()
1400 }
1401 break;
1402
1403 // Move selection to next node whose 1st character matches
1404 // the keycode
1405 default:
1406 {
1407 int cnt = (int) mLines.GetCount();
1408 bool found = false;
1409
1410 // Search the entire list if none is currently selected
1411 if (line == wxNOT_FOUND)
1412 {
1413 line = cnt;
1414 }
1415 else
1416 {
1417 // Search from the node following the current one
1418 for (int i = line + 1; i < cnt; i++)
1419 {
1420 wxString label;
1421
1422 // Get the string to search based on view type
1423 if (mViewType == ViewByTree)
1424 {
1425 label = GetLabel(LineToIndex(i));
1426 }
1427 else if (mViewType == ViewByName)
1428 {
1429 label = GetFullLabel(LineToIndex(i));
1430 }
1431 else if (mViewType == ViewByKey)
1432 {
1433 label = GetKey(LineToIndex(i));
1434 }
1435
1436 // Move selection if they match
1437 if (label.Left(1).IsSameAs(keycode, false))
1438 {
1439 SelectNode(LineToIndex(i));
1440
1441 found = true;
1442
1443 break;
1444 }
1445 }
1446 }
1447
1448 // A match wasn't found
1449 if (!found)
1450 {
1451 // So scan from the start of the list to the current node
1452 for (int i = 0; i < line; i++)
1453 {
1454 wxString label;
1455
1456 // Get the string to search based on view type
1457 if (mViewType == ViewByTree)
1458 {
1459 label = GetLabel(LineToIndex(i));
1460 }
1461 else if (mViewType == ViewByName)
1462 {
1463 label = GetFullLabel(LineToIndex(i));
1464 }
1465 else if (mViewType == ViewByKey)
1466 {
1467 label = GetKey(LineToIndex(i));
1468 }
1469
1470 // Move selection if they match
1471 if (label.Left(1).IsSameAs(keycode, false))
1472 {
1473 SelectNode(LineToIndex(i));
1474
1475 found = true;
1476
1477 break;
1478 }
1479 }
1480 }
1481
1482 // A node wasn't found so allow further processing
1483 if (!found) {
1484 event.Skip();
1485 }
1486
1487 // Otherwise, further processing of the event is not wanted
1488 // (we didn't call event.Skip()
1489 }
1490 }
1491 }
1492
1493 //
1494 // Handle the wxEVT_LEFT_DOWN event
1495 //
1496 void
1497 KeyView::OnLeftDown(wxMouseEvent & event)
1498 {
1499 // Only check if for tree view
1500 if (mViewType != ViewByTree)
1501 {
1502 // Allow further processing (important for focus handling)
1503 event.Skip();
1504
1505 return;
1506 }
1507
1508 // Get the mouse position when the button was pressed
1509 wxPoint pos = event.GetPosition();
1510
1511 // And see if it was on a line within the view
1512 int line = HitTest(pos);
1513
1514 // It was on a line
1515 if (line != wxNOT_FOUND)
1516 {
1517 KeyNode *node = mLines[line];
1518
1519 // Toggle the open state if this is a parent node
1520 if (node->isparent)
1521 {
1522 // Toggle state
1523 node->isopen = !node->isopen;
1524
1525 // Don't want the view to scroll vertically, so remember the current
1526 // top line.
1527 size_t topline = GetVisibleBegin();
1528
1529 // Refresh the view now that the number of lines have changed
1530 RefreshLines();
1531
1532 // Reset the original top line
1533 ScrollToLine(topline);
1534
1535 // And make sure current line is still selected
1536 SelectNode(LineToIndex(line));
1537 }
1538 }
1539
1540 // Allow further processing (important for focus handling)
1541 event.Skip();
1542 }
1543
1544 //
1545 // Sort compare function for tree view
1546 //
1547 // We want to leave the "menu" nodes alone as they are in the
1548 // order as they appear in the menus. But, we want to sort the
1549 // "command" nodes.
1550 //
1551 // To accomplish this, we prepend each label with it's line number
1552 // (in hex) for "menu" nodes. This ensures they will remain in
1553 // their original order.
1554 //
1555 // We prefix all "command" nodes with "ffffffff" (highest hex value)
1556 // to allow the sort to reorder them as needed.
1557 //
1558 int
1559 KeyView::CmpKeyNodeByTree(KeyNode ***n1, KeyNode ***n2)
1560 {
1561 KeyNode *t1 = (**n1);
1562 KeyNode *t2 = (**n2);
1563 wxString k1 = t1->label;
1564 wxString k2 = t2->label;
1565
1566 // This is a "command" node if its category is "Command"
1567 // and it is a child of the "Command" category. This latter
1568 // test ensures that the "Command" parent will be handled
1569 // as a "menu" node and remain at the bottom of the list.
1570 if (t1->category == _("Command") && !t1->isparent)
1571 {
1572 // A "command" node, so prepend the highest hex value
1573 k1.Printf(wxT("ffffffff%s"), t1->label.c_str());
1574 }
1575 else
1576 {
1577 // A "menu" node, so prepend the line number
1578 k1.Printf(wxT("%08x%s"), t1->line, t1->label.c_str());
1579 }
1580
1581 // See above for explanation
1582 if (t2->category == _("Command") && !t2->isparent)
1583 {
1584 // A "command" node, so prepend the highest hex value
1585 k2.Printf(wxT("ffffffff%s"), t2->label.c_str());
1586 }
1587 else
1588 {
1589 // A "menu" node, so prepend the line number
1590 k2.Printf(wxT("%08x%s"), t2->line, t2->label.c_str());
1591 }
1592
1593 // See wxWidgets documentation for explanation of comparison results.
1594 // These will produce an ascending order.
1595 if (k1 < k2)
1596 {
1597 return -1;
1598 }
1599
1600 if (k1 > k2)
1601 {
1602 return 1;
1603 }
1604
1605 return 0;
1606 }
1607
1608 //
1609 // Sort compare function for command view
1610 //
1611 // Nothing special here, just a standard ascending sort.
1612 //
1613 int
1614 KeyView::CmpKeyNodeByName(KeyNode ***n1, KeyNode ***n2)
1615 {
1616 KeyNode *t1 = (**n1);
1617 KeyNode *t2 = (**n2);
1618 wxString k1 = t1->label;
1619 wxString k2 = t2->label;
1620
1621 // Prepend prefix if available
1622 if (!t1->prefix.IsEmpty())
1623 {
1624 k1 = t1->prefix + wxT(" - ") + k1;
1625 }
1626
1627 // Prepend prefix if available
1628 if (!t2->prefix.IsEmpty())
1629 {
1630 k2 = t2->prefix + wxT(" - ") + k2;
1631 }
1632
1633 // See wxWidgets documentation for explanation of comparison results.
1634 // These will produce an ascending order.
1635 if (k1 < k2)
1636 {
1637 return -1;
1638 }
1639
1640 if (k1 > k2)
1641 {
1642 return 1;
1643 }
1644
1645 return 0;
1646 }
1647
1648 //
1649 // Sort compare function for key view
1650 //
1651 // We want all nodes with key assignments to appear in ascending order
1652 // at the top of the list and all nodes without assignment to appear in
1653 // ascending order at the bottom of the list.
1654 //
1655 // We accomplish this by by prefixing all non-assigned entries with 0xff.
1656 // This will force them to the end, but still allow them to be sorted in
1657 // ascending order.
1658 //
1659 // The assigned entries simply get sorted as normal.
1660 //
1661 int
1662 KeyView::CmpKeyNodeByKey(KeyNode ***n1, KeyNode ***n2)
1663 {
1664 KeyNode *t1 = (**n1);
1665 KeyNode *t2 = (**n2);
1666 wxString k1 = t1->key;
1667 wxString k2 = t2->key;
1668
1669 // Left node is unassigned, so prefix it
1670 if(k1.IsEmpty())
1671 {
1672 k1 = wxT("\xff");
1673 }
1674
1675 // Right node is unassigned, so prefix it
1676 if(k2.IsEmpty())
1677 {
1678 k2 = wxT("\xff");
1679 }
1680
1681 // Add prefix if available
1682 if (!t1->prefix.IsEmpty())
1683 {
1684 k1 += t1->prefix + wxT(" - ");
1685 }
1686
1687 // Add prefix if available
1688 if (!t2->prefix.IsEmpty())
1689 {
1690 k2 += t2->prefix + wxT(" - ");
1691 }
1692
1693 // Add labels
1694 k1 += t1->label;
1695 k2 += t2->label;
1696
1697 // See wxWidgets documentation for explanation of comparison results.
1698 // These will produce an ascending order.
1699 if (k1 < k2)
1700 {
1701 return -1;
1702 }
1703
1704 if (k1 > k2)
1705 {
1706 return 1;
1707 }
1708
1709 return 0;
1710 }
1711
1712 #if wxUSE_ACCESSIBILITY
1713
1714 //
1715 // Return parenthood state of line
1716 //
1717 bool
1718 KeyView::HasChildren(int line)
1719 {
1720 // Make sure line is valid
1721 if (line < 0 || line >= (int) mLines.GetCount())
1722 {
1723 wxASSERT(false);
1724 return false;
1725 }
1726
1727 return mLines[line]->isparent;
1728 }
1729
1730 //
1731 // Returns espanded/collapsed state of line
1732 //
1733 bool
1734 KeyView::IsExpanded(int line)
1735 {
1736 // Make sure line is valid
1737 if (line < 0 || line >= (int) mLines.GetCount())
1738 {
1739 wxASSERT(false);
1740 return false;
1741 }
1742
1743 return mLines[line]->isopen;
1744 }
1745
1746 //
1747 // Returns the height of the line
1748 //
1749 wxCoord
1750 KeyView::GetLineHeight(int line)
1751 {
1752 // Make sure line is valid
1753 if (line < 0 || line >= (int) mLines.GetCount())
1754 {
1755 wxASSERT(false);
1756 return 0;
1757 }
1758
1759 return OnGetLineHeight(line);
1760 }
1761
1762 //
1763 // Returns the value to be presented to accessibility
1764 //
1765 // Currently, the command and key are both provided.
1766 //
1767 wxString
1768 KeyView::GetValue(int line)
1769 {
1770 // Make sure line is valid
1771 if (line < 0 || line >= (int) mLines.GetCount())
1772 {
1773 wxASSERT(false);
1774 return wxEmptyString;
1775 }
1776 int index = LineToIndex(line);
1777
1778 // Get the label and key values
1779 wxString value;
1780 if (mViewType == ViewByTree)
1781 {
1782 value = GetLabel(index);
1783 }
1784 else
1785 {
1786 value = GetFullLabel(index);
1787 }
1788 wxString key = GetKey(index);
1789
1790 // Add the key if it isn't empty
1791 if (!key.IsEmpty())
1792 {
1793 if (mViewType == ViewByKey)
1794 {
1795 value = key + wxT(" ") + value;
1796 }
1797 else
1798 {
1799 value = value + wxT(" ") + key;
1800 }
1801 }
1802
1803 return value;
1804 }
1805
1806 //
1807 // Returns the current view type
1808 //
1809 ViewByType
1810 KeyView::GetViewType()
1811 {
1812 return mViewType;
1813 }
1814
1815 // ============================================================================
1816 // Accessibility provider for the KeyView class
1817 // ============================================================================
1818 KeyViewAx::KeyViewAx(KeyView *view)
1819 : wxWindowAccessible(view)
1820 {
1821 mView = view;
1822 mLastId = -1;
1823 }
1824
1825 //
1826 // Send an event notification to accessibility that the view
1827 // has changed.
1828 //
1829 void
1830 KeyViewAx::ListUpdated()
1831 {
1832 NotifyEvent(wxACC_EVENT_OBJECT_REORDER,
1833 mView,
1834 wxOBJID_CLIENT,
1835 0);
1836 }
1837
1838 //
1839 // Inform accessibility a new line has been selected and/or a previously
1840 // selected line is being unselected
1841 //
1842 void
1843 KeyViewAx::SetCurrentLine(int line)
1844 {
1845 // Only send selection remove notification if a line was
1846 // previously selected
1847 if (mLastId != -1)
1848 {
1849 NotifyEvent(wxACC_EVENT_OBJECT_SELECTIONREMOVE,
1850 mView,
1851 wxOBJID_CLIENT,
1852 mLastId);
1853 }
1854
1855 // Nothing is selected now
1856 mLastId = -1;
1857
1858 // Just clearing selection
1859 if (line != wxNOT_FOUND)
1860 {
1861 // Convert line number to childId
1862 LineToId(line, mLastId);
1863
1864 // Send notifications that the line has focus
1865 NotifyEvent(wxACC_EVENT_OBJECT_FOCUS,
1866 mView,
1867 wxOBJID_CLIENT,
1868 mLastId);
1869
1870 // And is selected
1871 NotifyEvent(wxACC_EVENT_OBJECT_SELECTION,
1872 mView,
1873 wxOBJID_CLIENT,
1874 mLastId);
1875 }
1876 }
1877
1878 //
1879 // Convert the childId to a line number and return FALSE if it
1880 // represents a child or TRUE if it a line
1881 //
1882 bool
1883 KeyViewAx::IdToLine(int childId, int & line)
1884 {
1885 if (childId == wxACC_SELF)
1886 {
1887 return false;
1888 }
1889
1890 // Convert to line
1891 line = childId - 1;
1892
1893 // Make sure id is valid
1894 if (line < 0 || line >= (int) mView->GetLineCount())
1895 {
1896 // Indicate the control itself in this case
1897 return false;
1898 }
1899
1900 return true;
1901 }
1902
1903 //
1904 // Convert the line number to a childId.
1905 //
1906 bool
1907 KeyViewAx::LineToId(int line, int & childId)
1908 {
1909 // Make sure line is valid
1910 if (line < 0 || line >= (int) mView->GetLineCount())
1911 {
1912 // Indicate the control itself in this case
1913 childId = wxACC_SELF;
1914 return false;
1915 }
1916
1917 // Convert to line
1918 childId = line + 1;
1919
1920 return true;
1921 }
1922
1923 // Can return either a child object, or an integer
1924 // representing the child element, starting from 1.
1925 wxAccStatus
1926 KeyViewAx::HitTest(const wxPoint & pt, int *childId, wxAccessible **childObject)
1927 {
1928 // Just to be safe
1929 *childObject = NULL;
1930
1931 wxPoint pos = mView->ScreenToClient(pt);
1932
1933 // See if it's on a line within the view
1934 int line = mView->HitTest(pos);
1935
1936 // It was on a line
1937 if (line != wxNOT_FOUND)
1938 {
1939 LineToId(line, *childId);
1940 return wxACC_OK;
1941 }
1942
1943 // Let the base class handle it
1944 return wxACC_NOT_IMPLEMENTED;
1945 }
1946
1947 // Retrieves the address of an IDispatch interface for the specified child.
1948 // All objects must support this property.
1949 wxAccStatus
1950 KeyViewAx::GetChild(int childId, wxAccessible** child)
1951 {
1952 if (childId == wxACC_SELF)
1953 {
1954 *child = this;
1955 }
1956 else
1957 {
1958 *child = NULL;
1959 }
1960
1961 return wxACC_OK;
1962 }
1963
1964 // Gets the number of children.
1965 wxAccStatus
1966 KeyViewAx::GetChildCount(int *childCount)
1967 {
1968 *childCount = (int) mView->GetLineCount();
1969
1970 return wxACC_OK;
1971 }
1972
1973 // Gets the default action for this object (0) or > 0 (the action for a child).
1974 // Return wxACC_OK even if there is no action. actionName is the action, or the empty
1975 // string if there is no action.
1976 // The retrieved string describes the action that is performed on an object,
1977 // not what the object does as a result. For example, a toolbar button that prints
1978 // a document has a default action of "Press" rather than "Prints the current document."
1979 wxAccStatus
1980 KeyViewAx::GetDefaultAction(int WXUNUSED(childId), wxString *actionName)
1981 {
1982 actionName->Clear();
1983
1984 return wxACC_OK;
1985 }
1986
1987 // Returns the description for this object or a child.
1988 wxAccStatus
1989 KeyViewAx::GetDescription(int WXUNUSED(childId), wxString *description)
1990 {
1991 description->Clear();
1992
1993 return wxACC_OK;
1994 }
1995
1996 // Returns help text for this object or a child, similar to tooltip text.
1997 wxAccStatus
1998 KeyViewAx::GetHelpText(int WXUNUSED(childId), wxString *helpText)
1999 {
2000 helpText->Clear();
2001
2002 return wxACC_OK;
2003 }
2004
2005 // Returns the keyboard shortcut for this object or child.
2006 // Return e.g. ALT+K
2007 wxAccStatus
2008 KeyViewAx::GetKeyboardShortcut(int WXUNUSED(childId), wxString *shortcut)
2009 {
2010 shortcut->Clear();
2011
2012 return wxACC_OK;
2013 }
2014
2015 // Returns the rectangle for this object (id = 0) or a child element (id > 0).
2016 // rect is in screen coordinates.
2017 wxAccStatus
2018 KeyViewAx::GetLocation(wxRect & rect, int elementId)
2019 {
2020 int line;
2021
2022 if (IdToLine(elementId, line))
2023 {
2024 if (!mView->IsVisible(line))
2025 {
2026 return wxACC_FAIL;
2027 }
2028
2029 wxRect rectLine;
2030
2031 rectLine.width = mView->GetClientSize().GetWidth();
2032
2033 // iterate over all visible lines
2034 for (int i = (int) mView->GetVisibleBegin(); i <= line; i++)
2035 {
2036 wxCoord hLine = mView->GetLineHeight(i);
2037
2038 rectLine.height = hLine;
2039
2040 rect = rectLine;
2041 wxPoint margins = mView->GetMargins();
2042 rect.Deflate(margins.x, margins.y);
2043 rectLine.y += hLine;
2044 }
2045
2046 rect.SetPosition(mView->ClientToScreen(rect.GetPosition()));
2047 }
2048 else
2049 {
2050 rect = mView->GetRect();
2051 rect.SetPosition(mView->GetParent()->ClientToScreen(rect.GetPosition()));
2052 }
2053
2054 return wxACC_OK;
2055 }
2056
2057 wxAccStatus
2058 KeyViewAx::Navigate(wxNavDir WXUNUSED(navDir),
2059 int WXUNUSED(fromId),
2060 int *WXUNUSED(toId),
2061 wxAccessible **WXUNUSED(toObject))
2062 {
2063 return wxACC_NOT_IMPLEMENTED;
2064 }
2065
2066 // Gets the name of the specified object.
2067 wxAccStatus
2068 KeyViewAx::GetName(int childId, wxString *name)
2069 {
2070 int line;
2071
2072 if (!IdToLine(childId, line))
2073 {
2074 *name = mView->GetName();
2075 }
2076 else
2077 {
2078 if (IdToLine(childId, line))
2079 {
2080 *name = mView->GetValue(line);
2081 }
2082 }
2083
2084 return wxACC_OK;
2085 }
2086
2087 wxAccStatus
2088 KeyViewAx::GetParent(wxAccessible ** WXUNUSED(parent))
2089 {
2090 return wxACC_NOT_IMPLEMENTED;
2091 }
2092
2093 // Returns a role constant.
2094 wxAccStatus
2095 KeyViewAx::GetRole(int childId, wxAccRole *role)
2096 {
2097 if (childId == wxACC_SELF)
2098 {
2099 #if defined(__WXMSW__)
2100 *role = mView->GetViewType() == ViewByTree ? wxROLE_SYSTEM_OUTLINE : wxROLE_SYSTEM_LIST;
2101 #endif
2102
2103 #if defined(__WXMAC__)
2104 *role = wxROLE_SYSTEM_GROUPING;
2105 #endif
2106 }
2107 else
2108 {
2109 #if defined(__WXMAC__)
2110 *role = wxROLE_SYSTEM_TEXT;
2111 #else
2112 *role = mView->GetViewType() == ViewByTree ? wxROLE_SYSTEM_OUTLINEITEM : wxROLE_SYSTEM_LISTITEM;
2113 #endif
2114 }
2115
2116 return wxACC_OK;
2117 }
2118
2119 // Gets a variant representing the selected children
2120 // of this object.
2121 // Acceptable values:
2122 // - a null variant (IsNull() returns TRUE)
2123 // - a list variant (GetType() == wxT("list"))
2124 // - an integer representing the selected child element,
2125 // or 0 if this object is selected (GetType() == wxT("long"))
2126 // - a "void*" pointer to a wxAccessible child object
2127 wxAccStatus
2128 KeyViewAx::GetSelections(wxVariant *selections)
2129 {
2130 int id;
2131
2132 LineToId(mView->GetSelection(), id);
2133
2134 *selections = (long) id;
2135
2136 return wxACC_OK;
2137 }
2138
2139 // Returns a state constant.
2140 wxAccStatus
2141 KeyViewAx::GetState(int childId, long *state)
2142 {
2143 int flag = wxACC_STATE_SYSTEM_FOCUSABLE;
2144 int line;
2145
2146 if (!IdToLine(childId, line))
2147 {
2148 *state = wxACC_STATE_SYSTEM_FOCUSABLE; // |
2149 //mView->FindFocus() == mView ? wxACC_STATE_SYSTEM_FOCUSED : 0;
2150 return wxACC_OK;
2151 }
2152
2153 #if defined(__WXMSW__)
2154 int selected = mView->GetSelection();
2155
2156 flag |= wxACC_STATE_SYSTEM_SELECTABLE;
2157
2158 if (line == selected)
2159 {
2160 flag |= wxACC_STATE_SYSTEM_FOCUSED |
2161 wxACC_STATE_SYSTEM_SELECTED;
2162 }
2163
2164 if (mView->HasChildren(line))
2165 {
2166 flag |= mView->IsExpanded(line) ?
2167 wxACC_STATE_SYSTEM_EXPANDED :
2168 wxACC_STATE_SYSTEM_COLLAPSED;
2169 }
2170 #endif
2171
2172 #if defined(__WXMAC__1)
2173 if (mGrid->IsInSelection(row, col))
2174 {
2175 flag |= wxACC_STATE_SYSTEM_SELECTED;
2176 }
2177
2178 if (mGrid->GetGridCursorRow() == row && mGrid->GetGridCursorCol() == col)
2179 {
2180 flag |= wxACC_STATE_SYSTEM_FOCUSED;
2181 }
2182
2183 if (mGrid->IsReadOnly(row, col))
2184 {
2185 flag |= wxACC_STATE_SYSTEM_UNAVAILABLE;
2186 }
2187 #endif
2188
2189 *state = flag;
2190
2191 return wxACC_OK;
2192 }
2193
2194 // Returns a localized string representing the value for the object
2195 // or child.
2196 wxAccStatus
2197 KeyViewAx::GetValue(int childId, wxString *strValue)
2198 {
2199 int line;
2200
2201 strValue->Clear();
2202
2203 if (!IdToLine(childId, line))
2204 {
2205 return wxACC_NOT_IMPLEMENTED;
2206 }
2207
2208 #if defined(__WXMSW__)
2209 if (mView->GetViewType() == ViewByTree)
2210 {
2211 KeyNode *node = mView->mLines[line];
2212 strValue->Printf(wxT("%d"), node->depth - 1);
2213 }
2214
2215 // Don't set a value for the other view types
2216 return wxACC_NOT_IMPLEMENTED;
2217 #endif
2218
2219 #if defined(__WXMAC__)
2220 return GetName(childId, strValue);
2221 #endif
2222 }
2223
2224 #if defined(__WXMAC__)
2225 // Selects the object or child.
2226 wxAccStatus
2227 KeyViewAx::Select(int childId, wxAccSelectionFlags selectFlags)
2228 {
2229 #if 0
2230 int row;
2231 int col;
2232
2233 if (GetRowCol(childId, row, col))
2234 {
2235
2236 if (selectFlags & wxACC_SEL_TAKESELECTION)
2237 {
2238 mGrid->SetGridCursor(row, col);
2239 }
2240
2241 mGrid->SelectBlock(row, col, row, col, selectFlags & wxACC_SEL_ADDSELECTION);
2242 }
2243 #endif
2244 return wxACC_OK;
2245 }
2246 #endif
2247
2248 // Gets the window with the keyboard focus.
2249 // If childId is 0 and child is NULL, no object in
2250 // this subhierarchy has the focus.
2251 // If this object has the focus, child should be 'this'.
2252 wxAccStatus
2253 KeyViewAx::GetFocus(int * WXUNUSED(childId), wxAccessible **child)
2254 {
2255 *child = this;
2256
2257 return wxACC_OK;
2258 }
2259
2260 #endif // wxUSE_ACCESSIBILITY