Audacity 3.2.0
EqualizationCurvesDialog.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 EqualizationCurvesDialog.cpp
6
7 Mitch Golden
8 Vaughan Johnson (Preview)
9 Martyn Shaw (FIR filters, response curve, graphic EQ)
10
11 Paul Licameli split from Equalization.cpp
12
13**********************************************************************/
15#include "EffectUIServices.h"
16
17#include <wx/listctrl.h>
18#include "ShuttleGui.h"
19#include "AudacityMessageBox.h"
21
33 EqualizationCurvesDialog::OnListSelectionChange)
34 EVT_LIST_ITEM_DESELECTED(CurvesListID,
35 EqualizationCurvesDialog::OnListSelectionChange)
37
39 const TranslatableString &name, int options,
40 EQCurveArray &curves, int position
41) : wxDialogWrapper(parent, wxID_ANY, XO("Manage Curves List"),
42 wxDefaultPosition, wxDefaultSize,
43 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
44 , mName{ name }
45 , mOptions{ options }
46 , mCurves{ curves }
47{
48 SetLabel(XO("Manage Curves")); // Provide visual label
49 SetName(XO("Manage Curves List")); // Provide audible label
50 mParent = parent;
51 mPosition = position;
52 // make a copy of curves here to muck about with.
53 mEditCurves = curves;
54
55 Populate();
56 SetMinSize(GetSize());
57}
58
60{
61}
62
65{
66 //------------------------- Main section --------------------
69 // ----------------------- End of main section --------------
70}
71
74{
75 S.StartHorizontalLay(wxEXPAND);
76 {
77 S.StartStatic(XO("&Curves"), 1);
78 {
79 mList = S.Id(CurvesListID)
80 .Style( wxLC_REPORT | wxLC_HRULES | wxLC_VRULES )
81 .AddListControlReportMode({
82 { XO("Curve Name"), wxLIST_FORMAT_RIGHT }
83 });
84 }
85 S.EndStatic();
86 S.StartVerticalLay(0);
87 {
88 S.Id(UpButtonID).AddButton(XXO("Move &Up"), wxALIGN_LEFT);
89 S.Id(DownButtonID).AddButton(XXO("Move &Down"), wxALIGN_LEFT);
90 S.Id(RenameButtonID).AddButton(XXO("&Rename..."), wxALIGN_LEFT);
91 S.Id(DeleteButtonID).AddButton(XXO("D&elete..."), wxALIGN_LEFT);
92 S.Id(ImportButtonID).AddButton(XXO("I&mport..."), wxALIGN_LEFT);
93 S.Id(ExportButtonID).AddButton(XXO("E&xport..."), wxALIGN_LEFT);
94 S.Id(LibraryButtonID).AddButton(XXO("&Get More..."), wxALIGN_LEFT);
95 S.Id(DefaultsButtonID).AddButton(XXO("De&faults"), wxALIGN_LEFT);
96 }
97 S.EndVerticalLay();
98 }
99 S.EndHorizontalLay();
100 S.AddStandardButtons();
101 S.StartStatic(XO("Help"));
102 S.AddConstTextBox( {}, XO("Rename 'unnamed' to save a new entry.\n'OK' saves all changes, 'Cancel' doesn't."));
103 S.EndStatic();
105 Fit();
106
107 return;
108}
109
111{
112 mList->DeleteAllItems();
113 for (unsigned int i = 0; i < mEditCurves.size(); i++)
114 mList->InsertItem(i, mEditCurves[i].Name);
115 mList->SetColumnWidth(0, wxLIST_AUTOSIZE);
116 int curvesWidth = mList->GetColumnWidth(0);
117 mList->SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER);
118 int headerWidth = mList->GetColumnWidth(0);
119 mList->SetColumnWidth(0, wxMax(headerWidth, curvesWidth));
120 // use 'position' to set focus
121 mList->EnsureVisible(position);
122 mList->SetItemState(position, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
123}
124
125namespace EQUtils
126{
128 const TranslatableString& name, const TranslatableString& msg,
129 const TranslatableString& titleStr, long style = wxOK | wxCENTRE)
130{
131 // Compare with EffectUIServices::DoMessageBox
132 auto title = titleStr.empty() ? name : XO("%s: %s").Format(name, titleStr);
133 return AudacityMessageBox(msg, title, style, nullptr);
134}
135} // namespace
136
137void EqualizationCurvesDialog::OnUp(wxCommandEvent & WXUNUSED(event))
138{
139 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
140 if ( item == -1 )
141 return; // no items selected
142 if( item == 0 )
143 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); // top item selected, can't move up
144 int state;
145 while( item != -1 )
146 {
147 if ( item == mList->GetItemCount()-1)
148 { // 'unnamed' always stays at the bottom
150 XO("'unnamed' always stays at the bottom of the list"),
151 XO("'unnamed' is special") ); // these could get tedious!
152 return;
153 }
154 state = mList->GetItemState(item-1, wxLIST_STATE_SELECTED);
155 if ( state != wxLIST_STATE_SELECTED )
156 { // swap this with one above but only if it isn't selected
157 EQCurve temp(wxT("temp"));
158 temp.Name = mEditCurves[item].Name;
159 temp.points = mEditCurves[item].points;
160 mEditCurves[item].Name = mEditCurves[item-1].Name;
161 mEditCurves[item].points = mEditCurves[item-1].points;
162 mEditCurves[item-1].Name = temp.Name;
163 mEditCurves[item-1].points = temp.points;
164 wxString sTemp = mList->GetItemText(item);
165 mList->SetItem(item, 0, mList->GetItemText(item-1));
166 mList->SetItem(item-1, 0, sTemp);
167 mList->SetItemState(item, 0, wxLIST_STATE_SELECTED);
168 mList->SetItemState(item-1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
169 }
170 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
171 }
172}
173
174void EqualizationCurvesDialog::OnDown(wxCommandEvent & WXUNUSED(event))
175{ // looks harder than OnUp as we need to seek backwards up the list, hence GetPreviousItem
176 long item = GetPreviousItem(mList->GetItemCount());
177 if( item == -1 )
178 return; // nothing selected
179 int state;
180 while( item != -1 )
181 {
182 if( (item != mList->GetItemCount()-1) && (item != mList->GetItemCount()-2) )
183 { // can't move 'unnamed' down, or the one above it
184 state = mList->GetItemState(item+1, wxLIST_STATE_SELECTED);
185 if ( state != wxLIST_STATE_SELECTED )
186 { // swap this with one below but only if it isn't selected
187 EQCurve temp(wxT("temp"));
188 temp.Name = mEditCurves[item].Name;
189 temp.points = mEditCurves[item].points;
190 mEditCurves[item].Name = mEditCurves[item+1].Name;
191 mEditCurves[item].points = mEditCurves[item+1].points;
192 mEditCurves[item+1].Name = temp.Name;
193 mEditCurves[item+1].points = temp.points;
194 wxString sTemp = mList->GetItemText(item);
195 mList->SetItem(item, 0, mList->GetItemText(item+1));
196 mList->SetItem(item+1, 0, sTemp);
197 mList->SetItemState(item, 0, wxLIST_STATE_SELECTED);
198 mList->SetItemState(item+1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
199 }
200 }
201 item = GetPreviousItem(item);
202 }
203}
204
205long EqualizationCurvesDialog::GetPreviousItem(long item) // wx doesn't have this
206{
207 long lastItem = -1;
208 long itemTemp = mList->GetNextItem(-1, wxLIST_NEXT_ALL,
209 wxLIST_STATE_SELECTED);
210 while( (itemTemp != -1) && (itemTemp < item) )
211 {
212 lastItem = itemTemp;
213 itemTemp = mList->GetNextItem(itemTemp, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
214 }
215 return lastItem;
216}
217
218// Rename curve/curves
219void EqualizationCurvesDialog::OnRename(wxCommandEvent & WXUNUSED(event))
220{
221 wxString name;
222 int numCurves = mEditCurves.size();
223 int curve = 0;
224
225 // Setup list of characters that aren't allowed
226 wxArrayStringEx exclude{
227 wxT("<") ,
228 wxT(">") ,
229 wxT("'") ,
230 wxT("\"") ,
231 };
232
233 // Get the first one to be renamed
234 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
235 long firstItem = item; // for reselection with PopulateList
236 while(item >= 0)
237 {
238 // Prompt the user until a valid name is enter or cancelled
239 bool overwrite = false;
240 bool bad = true;
241 while( bad ) // Check for an unacceptable duplicate
242 { // Show the dialog and bail if the user cancels
243 bad = false;
244 // build the dialog
245 AudacityTextEntryDialog dlg( this,
246 XO("Rename '%s' to...").Format( mEditCurves[ item ].Name ),
247 XO("Rename...") );
248 dlg.SetTextValidator( wxFILTER_EXCLUDE_CHAR_LIST );
249 dlg.SetName(
250 wxString::Format( _("Rename '%s'"), mEditCurves[ item ].Name ) );
251 wxTextValidator *tv = dlg.GetTextValidator();
252 tv->SetExcludes( exclude ); // Tell the validator about excluded chars
253 if( dlg.ShowModal() == wxID_CANCEL )
254 {
255 bad = true;
256 break;
257 }
258
259 // Extract the name from the dialog
260 name = dlg.GetValue();
261
262 // Search list of curves for a duplicate name
263 for( curve = 0; curve < numCurves; curve++ )
264 {
265 wxString temp = mEditCurves[ curve ].Name;
266 if( name == mEditCurves[ curve ].Name ) // case sensitive
267 {
268 bad = true;
269 if( curve == item ) // trying to rename a curve with the same name
270 {
272 XO("Name is the same as the original one"),
273 XO("Same name"),
274 wxOK );
275 break;
276 }
277 int answer = EQUtils::DoMessageBox(mName,
278 XO("Overwrite existing curve '%s'?").Format( name ),
279 XO("Curve exists"),
280 wxYES_NO);
281 if (answer == wxYES)
282 {
283 bad = false;
284 overwrite = true; // we are going to overwrite the one with this name
285 break;
286 }
287 }
288 }
289 if( name.empty() || name == wxT("unnamed") )
290 bad = true;
291 }
292
293 // if bad, we cancelled the rename dialog, so nothing to do.
294 if( bad == true )
295 ;
296 else if(overwrite){
297 // Overwrite another curve.
298 // JKC: because 'overwrite' is true, 'curve' is the number of the curve that
299 // we are about to overwrite.
300 mEditCurves[ curve ].Name = name;
301 mEditCurves[ curve ].points = mEditCurves[ item ].points;
302 // if renaming the unnamed item, then select it,
303 // otherwise get rid of the item we've renamed.
304 if( item == (numCurves-1) )
305 mList->SetItem(curve, 0, name);
306 else
307 {
308 mEditCurves.erase( mEditCurves.begin() + item );
309 numCurves--;
310 }
311 }
312 else if( item == (numCurves-1) ) // renaming 'unnamed'
313 { // Create a NEW entry
314 mEditCurves.push_back( EQCurve( wxT("unnamed") ) );
315 // Copy over the points
316 mEditCurves[ numCurves ].points = mEditCurves[ numCurves - 1 ].points;
317 // Give the original unnamed entry the NEW name
318 mEditCurves[ numCurves - 1 ].Name = name;
319 numCurves++;
320 }
321 else // just rename (the 'normal' case)
322 {
323 mEditCurves[ item ].Name = name;
324 mList->SetItem(item, 0, name);
325 }
326 // get next selected item
327 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
328 }
329
330 PopulateList(firstItem); // Note: only saved to file when you OK out of the dialog
331 return;
332}
333
334// Delete curve/curves
335void EqualizationCurvesDialog::OnDelete(wxCommandEvent & WXUNUSED(event))
336{
337 // We could count them here
338 // And then put in a 'Delete N items?' prompt.
339
340#if 0 // 'one at a time' prompt code
341 // Get the first one to be deleted
342 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
343 // Take care, mList and mEditCurves will get out of sync as curves are deleted
344 int deleted = 0;
345 long highlight = -1;
346
347 while(item >= 0)
348 {
349 if(item == mList->GetItemCount()-1) //unnamed
350 {
352 XO("You cannot delete the 'unnamed' curve."),
353 XO("Can't delete 'unnamed'") );
354 }
355 else
356 {
357 // Create the prompt
358 auto quest = XO("Delete '%s'?")
359 .Format(mEditCurves[ item-deleted ].Name);
360
361 // Ask for confirmation before removal
363 quest,
364 XO("Confirm Deletion") );
365 if( ans == wxYES )
366 { // Remove the curve from the array
367 mEditCurves.erase( mEditCurves.begin() + (item - deleted) );
368 deleted++;
369 }
370 else
371 highlight = item-deleted; // if user presses 'No', select that curve
372 }
373 // get next selected item
374 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
375 }
376
377 if(highlight == -1)
378 PopulateList(mEditCurves.size()-1); // set 'unnamed' as the selected curve
379 else
380 PopulateList(highlight); // user said 'No' to deletion
381#else // 'DELETE all N' code
382 int count = mList->GetSelectedItemCount();
383 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
384 // Create the prompt
385 TranslatableString quest;
386 if( count > 1 )
387 quest = XO("Delete %d items?").Format( count );
388 else
389 if( count == 1 )
390 quest = XO("Delete '%s'?").Format( mEditCurves[ item ].Name );
391 else
392 return;
393 // Ask for confirmation before removal
395 quest,
396 XO("Confirm Deletion"),
397 wxYES_NO | wxCENTRE );
398 if( ans == wxYES )
399 { // Remove the curve(s) from the array
400 // Take care, mList and mEditCurves will get out of sync as curves are deleted
401 int deleted = 0;
402 while(item >= 0)
403 {
404 // TODO: Migrate to the standard "Manage" dialog.
405 if(item == mList->GetItemCount()-1) //unnamed
406 {
408 XO("You cannot delete the 'unnamed' curve, it is special."),
409 XO("Can't delete 'unnamed'"));
410 }
411 else
412 {
413 mEditCurves.erase( mEditCurves.begin() + item - deleted );
414 deleted++;
415 }
416 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
417 }
418 PopulateList(mEditCurves.size() - 1); // set 'unnamed' as the selected curve
419 }
420#endif
421}
422
424{
425 static const FileNames::FileTypes results{
427 };
428 return results;
429}
430
431void EqualizationCurvesDialog::OnImport( wxCommandEvent & WXUNUSED(event))
432{
433 FileDialogWrapper filePicker(
434 this,
435 XO("Choose an EQ curve file"), FileNames::DataDir(), wxT(""),
436 XMLtypes() );
437 wxString fileName;
438 if( filePicker.ShowModal() == wxID_CANCEL)
439 return;
440 else
441 fileName = filePicker.GetPath();
443 .LoadCurves(fileName, true);
444 PopulateList(0); // update the EqualizationCurvesDialog dialog
445 return;
446}
447
448void EqualizationCurvesDialog::OnExport( wxCommandEvent & WXUNUSED(event))
449{
450 FileDialogWrapper filePicker(this, XO("Export EQ curves as..."),
451 FileNames::DataDir(), wxT(""),
452 XMLtypes(),
453 wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER); // wxFD_CHANGE_DIR?
454 wxString fileName;
455 if( filePicker.ShowModal() == wxID_CANCEL)
456 return;
457 else
458 fileName = filePicker.GetPath();
459
460 EQCurveArray exportCurves; // Copy selected curves to export
461 exportCurves.clear();
462 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
463 int i=0;
464 while(item >= 0)
465 {
466 if(item != mList->GetItemCount()-1) // not 'unnamed'
467 {
468 exportCurves.push_back(mEditCurves[item].Name);
469 exportCurves[i].points = mEditCurves[item].points;
470 i++;
471 }
472 else
474 XO("You cannot export 'unnamed' curve, it is special."),
475 XO("Cannot Export 'unnamed'") );
476 // get next selected item
477 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
478 }
479 if(i>0)
480 {
481 EQCurveWriter{ exportCurves }.SaveCurves(fileName);
482 auto message = XO("%d curves exported to %s").Format( i, fileName );
484 message,
485 XO("Curves exported") );
486 }
487 else
489 XO("No curves exported"),
490 XO("No curves exported") );
491}
492
493void EqualizationCurvesDialog::OnLibrary( wxCommandEvent & WXUNUSED(event))
494{
495 // full path to wiki.
496 wxLaunchDefaultBrowser(wxT("https://wiki.audacityteam.org/wiki/EQCurvesDownload"));
497}
498
499void EqualizationCurvesDialog::OnDefaults( wxCommandEvent & WXUNUSED(event))
500{
501 // we expect this to fail in LoadCurves (due to a lack of path) and handle that there
503 .LoadCurves( wxT("EQDefaultCurves.xml") );
504 PopulateList(0); // update the EqualizationCurvesDialog dialog
505}
506
507void EqualizationCurvesDialog::OnOK(wxCommandEvent & WXUNUSED(event))
508{
509 {
510 // Make a backup of the current curves
511 wxString backupPlace =
512 wxFileName( FileNames::DataDir(), wxT("EQBackup.xml") ).GetFullPath();
513 EQCurveWriter writer{ mCurves };
514 writer.SaveCurves(backupPlace);
515 // Load back into the main dialog
517 // Save to default place
518 writer.SaveCurves();
519 } // scope of writer
520 EQCurveReader{ mCurves, mName, mOptions }.LoadCurves();
521
522 // Select something sensible
523 long item = mList->GetNextItem(-1,
524 wxLIST_NEXT_ALL,
525 wxLIST_STATE_SELECTED);
526 if (item == -1)
527 item = mList->GetItemCount()-1; // nothing selected, default to 'unnamed'
528 mItem = item;
529 EndModal(true);
530}
531
533{
534 const bool enable = mList->GetSelectedItemCount() > 0;
535 static const int ids[] = {
540 };
541 for (auto id : ids)
542 FindWindowById(id, this)->Enable(enable);
543}
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
END_EVENT_TABLE()
@ UpButtonID
@ ImportButtonID
@ DownButtonID
@ RenameButtonID
@ DefaultsButtonID
@ DeleteButtonID
@ ExportButtonID
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
std::vector< EQCurve > EQCurveArray
static const FileNames::FileTypes & XMLtypes()
EVT_LIST_ITEM_SELECTED(CurvesListID, EqualizationCurvesDialog::OnListSelectionChange) EVT_LIST_ITEM_DESELECTED(CurvesListID
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define _(s)
Definition: Internat.h:73
static const auto title
@ eIsCreating
Definition: ShuttleGui.h:37
wxString name
Definition: TagsEditor.cpp:166
#define S(N)
Definition: ToChars.cpp:64
Wrap wxTextEntryDialog so that caption IS translatable.
One curve in a list.
std::vector< EQPoint > points
wxString Name
Deserializer of curves from XML files.
Serializer of curves into XML files.
void SaveCurves(const wxString &fileName={})
EqualizationCurvesDialog manages the available preset curves.
void OnDown(wxCommandEvent &event)
void PopulateOrExchange(ShuttleGui &S)
Defines the dialog and does data exchange with it.
void OnDelete(wxCommandEvent &event)
void OnOK(wxCommandEvent &event)
const TranslatableString & mName
void OnDefaults(wxCommandEvent &event)
void OnExport(wxCommandEvent &event)
void Populate()
Creates the dialog and its contents.
void OnImport(wxCommandEvent &event)
void OnRename(wxCommandEvent &event)
void OnUp(wxCommandEvent &event)
void OnListSelectionChange(wxListEvent &event)
void OnLibrary(wxCommandEvent &event)
virtual wxString GetPath() const
virtual int ShowModal()
FILES_API const FileType XMLFiles
Definition: FileNames.h:73
std::vector< FileType > FileTypes
Definition: FileNames.h:75
Abstract base class used in importing a file.
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
Holds a msgid for the translation catalog; may also bind format arguments.
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
int DoMessageBox(const TranslatableString &name, const TranslatableString &msg, const TranslatableString &titleStr, long style=wxOK|wxCENTRE)
FILES_API FilePath DataDir()
Audacity user data directory.