Audacity 3.2.0
TagsEditor.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 TagsEditor.cpp
6
7 Paul Licameli split from Tags.cpp
8
9 **********************************************************************/
10
11#include "TagsEditor.h"
12
13#include "SelectFile.h"
14#include "ShuttleGui.h"
16#include "widgets/Grid.h"
17#include "widgets/HelpSystem.h"
18#include "XMLFileReader.h"
19#include <wx/combobox.h>
20#include <wx/display.h>
21#include <wx/scrolbar.h>
22
23bool TagsEditorDialog::ShowEditDialog(Tags &tags, wxWindow *parent, const TranslatableString &title, bool force)
24{
25 if (force) {
26 TagsEditorDialog dlg(parent, title, &tags, true, true);
27
28 return dlg.ShowModal() == wxID_OK;
29 }
30
31 return true;
32}
33
34//
35// ComboEditor - Wrapper to prevent unwanted background erasure
36//
37
38class ComboEditor final : public wxGridCellChoiceEditor
39{
40public:
41 ComboEditor(const wxArrayString& choices, bool allowOthers = false)
42 : wxGridCellChoiceEditor(choices, allowOthers)
43 , m_choices{ choices }
44 , m_allowOthers{ allowOthers }
45 {
46 }
47
48 void PaintBackground(wxDC&, const wxRect& WXUNUSED(rectCell), const wxGridCellAttr & WXUNUSED(attr)) override
49 {
50 // Ignore it (a must on the Mac as the erasure causes problems.)
51 }
52
53 void SetParameters(const wxString& params) override
54 {
55 wxGridCellChoiceEditor::SetParameters(params);
56
57 // Refresh the wxComboBox with NEW values
58 if (Combo()) {
59 Combo()->Clear();
60 Combo()->Append(m_choices);
61 }
62 }
63
64 void SetSize(const wxRect& rectOrig) override
65 {
66 wxRect rect(rectOrig);
67 wxRect r = Combo()->GetRect();
68
69 // Center the combo box in or over the cell
70 rect.y -= (r.GetHeight() - rect.GetHeight()) / 2;
71 rect.height = r.GetHeight();
72
73 wxGridCellChoiceEditor::SetSize(rect);
74 }
75
76 // Fix for Bug 1389
77 // July 2016: ANSWER-ME: Does this need reporting upstream to wxWidgets?
78 virtual void StartingKey(wxKeyEvent& event) override
79 {
80 // Lifted from wxGridCellTextEditor and adapted to combo.
81
82 // [Below is comment from wxWidgets code]
83 // Since this is now happening in the EVT_CHAR event EmulateKeyPress is no
84 // longer an appropriate way to get the character into the text control.
85 // Do it ourselves instead. We know that if we get this far that we have
86 // a valid character, so not a whole lot of testing needs to be done.
87
88 //The only difference to wxGridCellTextEditor.
89 //wxTextCtrl* tc = (wxTextCtrl *)m_control;
90 wxComboBox * tc = Combo();
91 int ch;
92
93 bool isPrintable;
94
95 #if wxUSE_UNICODE
96 ch = event.GetUnicodeKey();
97 if ( ch != WXK_NONE )
98 isPrintable = true;
99 else
100 #endif // wxUSE_UNICODE
101 {
102 ch = event.GetKeyCode();
103 isPrintable = ch >= WXK_SPACE && ch < WXK_START;
104 }
105
106 switch (ch)
107 {
108 case WXK_DELETE:
109 // Delete the initial character when starting to edit with DELETE.
110 tc->Remove(0, 1);
111 break;
112
113 case WXK_BACK:
114 // Delete the last character when starting to edit with BACKSPACE.
115 {
116 const long pos = tc->GetLastPosition();
117 tc->Remove(pos - 1, pos);
118 }
119 break;
120
121 default:
122 if ( isPrintable )
123 tc->WriteText(static_cast<wxChar>(ch));
124 break;
125 }
126 }
127
128 // Clone is required by wxwidgets; implemented via copy constructor
129 wxGridCellEditor *Clone() const override
130 {
132 }
133
134private:
135 wxArrayString m_choices;
137};
138
139//
140// Editor
141//
142
143#define LABEL_ARTIST XO("Artist Name")
144#define LABEL_TITLE XO("Track Title")
145#define LABEL_ALBUM XO("Album Title")
146#define LABEL_TRACK XO("Track Number")
147#define LABEL_YEAR XO("Year")
148#define LABEL_GENRE XO("Genre")
149#define LABEL_COMMENTS XO("Comments")
150
159};
160
161static const struct
162{
164 wxString name;
165}
166labelmap[] =
167{
172 { LABEL_YEAR, TAG_YEAR },
176
177#define STATICCNT WXSIZEOF(labelmap)
178
179enum {
180 ClearID = 10000,
190
191BEGIN_EVENT_TABLE(TagsEditorDialog, wxDialogWrapper)
192 EVT_GRID_CELL_CHANGED(TagsEditorDialog::OnChange)
205 EVT_KEY_DOWN(TagsEditorDialog::OnKeyDown)
207
210 Tags * tags,
211 bool editTitle,
212 bool editTrack)
213: wxDialogWrapper(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize,
214 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
215 mTags(tags),
216 mEditTitle(editTitle),
217 mEditTrack(editTrack)
218{
219 SetName();
220
221 mGrid = NULL;
222
223 // Make a local copy of the passed in tags
224 mLocal = *mTags;
225
226 // Build, size, and position the dialog
227 ShuttleGui S(this, eIsCreating);
228 PopulateOrExchange(S);
229
230 TransferDataToWindow();
231
232 Layout();
233 Fit();
234 Center();
235 wxSize sz = GetSize();
236 SetSizeHints(sz.x, std::min(sz.y, 600));
237
238 // Restore the original tags because TransferDataToWindow() will be called again
239 mLocal.Clear();
240 mLocal = *mTags;
241
242 // Override size and position with last saved
243 wxRect r = GetRect();
244 gPrefs->Read(wxT("/TagsEditorDialog/x"), &r.x, r.x);
245 gPrefs->Read(wxT("/TagsEditorDialog/y"), &r.y, r.y);
246 gPrefs->Read(wxT("/TagsEditorDialog/width"), &r.width, r.width);
247 gPrefs->Read(wxT("/TagsEditorDialog/height"), &r.height, r.height);
248 //On multi-monitor systems, there's a chance the last saved window position is
249 //on a monitor that has been removed or is unavailable.
250 if (IsWindowRectValid(&r))
251 Move(r.GetPosition());
252
253 SetSize(r.GetSize());
254 Layout();
255
256 // Resize value column width based on width of columns and the vertical scrollbar
257 wxScrollBar sb(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL);
258 r = mGrid->GetClientRect();
259 r.width -= mGrid->GetColSize(0);
260 r.width -= sb.GetSize().GetWidth();
261 r.width -= 10;
262 r.width -= r.x;
263 mGrid->SetColSize(1, r.width);
264 //Bug 2038
265 mGrid->SetFocus();
266
267 // Load the genres
268 PopulateGenres();
269}
270
272{
273 // This DELETE is not needed because wxWidgets owns the grid.
274// DELETE mGrid;
275
276// TODO: Need to figure out if these should be deleted. Looks like the wxGrid
277// code takes ownership and uses reference counting, but there's been
278// cases where they show up as memory leaks.
279// PRL: Fixed the leaks, see commit c87eb0804bc5f40659b133cab6e2ade061959645
280// DELETE mStringRenderer;
281// DELETE mComboEditor;
282}
283
285{
286 bool bShow;
287 gPrefs->Read(wxT("/AudioFiles/ShowId3Dialog"), &bShow, true );
288
289 S.StartVerticalLay();
290 {
291 S.StartHorizontalLay(wxALIGN_LEFT, 0);
292 {
293 S.AddUnits(XO("Use arrow keys (or ENTER key after editing) to navigate fields."));
294 }
295 S.EndHorizontalLay();
296
297 if (mGrid == NULL) {
298 mGrid = safenew Grid(S.GetParent(),
299 wxID_ANY,
300 wxDefaultPosition,
301 wxDefaultSize,
302 wxSUNKEN_BORDER);
303
304 mGrid->RegisterDataType(wxT("Combo"),
305 (mStringRenderer = safenew wxGridCellStringRenderer),
306 (mComboEditor = safenew ComboEditor(wxArrayString(), true)));
307
308 mGrid->SetColLabelSize(mGrid->GetDefaultRowSize());
309
310 auto cs = transform_container<wxArrayStringEx>(
311 names, std::mem_fn( &TranslatableString::Translation ) );
312
313 // Build the initial (empty) grid
314 mGrid->CreateGrid(0, 2, wxGrid::wxGridSelectRows);
315 mGrid->SetRowLabelSize(0);
316 mGrid->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTER);
317 mGrid->SetColLabelValue(0, _("Tag"));
318 mGrid->SetColLabelValue(1, _("Value"));
319
320 // Resize the name column and set default row height.
321 wxComboBox tc(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, cs);
322 mGrid->SetColSize(0, tc.GetSize().x);
323 mGrid->SetColMinimalWidth(0, tc.GetSize().x);
324 }
325 S.Prop(1)
326 .Position(wxEXPAND | wxALL)
327 .AddWindow(mGrid);
328
329 S.StartMultiColumn(4, wxALIGN_CENTER);
330 {
331 S.Id(AddID).AddButton(XXO("&Add"));
332 S.Id(RemoveID).AddButton(XXO("&Remove"));
333 S.AddTitle( {} );
334 S.Id(ClearID).AddButton(XXO("Cl&ear"));
335 }
336 S.EndMultiColumn();
337
338 S.StartHorizontalLay(wxALIGN_CENTRE, 0);
339 {
340 S.StartStatic(XO("Genres"));
341 {
342 S.StartMultiColumn(4, wxALIGN_CENTER);
343 {
344 S.Id(EditID).AddButton(XXO("E&dit..."));
345 S.Id(ResetID).AddButton(XXO("Rese&t..."));
346 }
347 S.EndMultiColumn();
348 }
349 S.EndStatic();
350 S.StartStatic(XO("Template"));
351 {
352 S.StartMultiColumn(4, wxALIGN_CENTER);
353 {
354 S.Id(LoadID).AddButton(XXO("&Load..."));
355 S.Id(SaveID).AddButton(XXO("&Save..."));
356 S.AddTitle( {} );
357 S.Id(SaveDefaultsID).AddButton(XXO("Set De&fault"));
358 }
359 S.EndMultiColumn();
360 }
361 S.EndStatic();
362 }
363 S.EndHorizontalLay();
364 S.StartHorizontalLay(wxALIGN_LEFT, 0);
365 {
366 S.Id( DontShowID ).AddCheckBox( XXO("Don't show this when exporting audio"), !bShow );
367 }
368 S.EndHorizontalLay();
369 }
370 S.EndVerticalLay();
371
372 S.AddStandardButtons(eOkButton | eCancelButton | eHelpButton);
373}
374
375void TagsEditorDialog::OnDontShow( wxCommandEvent & Evt )
376{
377 bool bShow = !Evt.IsChecked();
378 gPrefs->Write(wxT("/AudioFiles/ShowId3Dialog"), bShow );
379 gPrefs->Flush();
380}
381
382void TagsEditorDialog::OnHelp(wxCommandEvent& WXUNUSED(event))
383{
384 HelpSystem::ShowHelp(this, L"Metadata_Editor", true);
385}
386
388{
389 int i, cnt = mGrid->GetNumberRows();
390
391 if (mGrid->IsCellEditControlShown()) {
392 mGrid->SaveEditControlValue();
393 mGrid->HideCellEditControl();
394 }
395
396 mLocal.Clear();
397 for (i = 0; i < cnt; i++) {
398 // Get tag name from the grid
399
400 auto n = mGrid->GetCellValue(i, 0);
401 wxString v = mGrid->GetCellValue(i, 1);
402
403 if (n.empty()) {
404 continue;
405 }
406
407 bool bSpecialTag = true;
408
409 // Map special tag names back to internal keys
410 if (n.CmpNoCase(LABEL_ARTIST.Translation()) == 0) {
411 n = TAG_ARTIST;
412 }
413 else if (n.CmpNoCase(LABEL_TITLE.Translation()) == 0) {
414 n = TAG_TITLE;
415 }
416 else if (n.CmpNoCase(LABEL_ALBUM.Translation()) == 0) {
417 n = TAG_ALBUM;
418 }
419 else if (n.CmpNoCase(LABEL_TRACK.Translation()) == 0) {
420 n = TAG_TRACK;
421 }
422 else if (n.CmpNoCase(LABEL_YEAR.Translation()) == 0) {
423 n = TAG_YEAR;
424 }
425 else if (n.CmpNoCase(LABEL_GENRE.Translation()) == 0) {
426 n = TAG_GENRE;
427 }
428 else if (n.CmpNoCase(LABEL_COMMENTS.Translation()) == 0) {
429 n = TAG_COMMENTS;
430 }
431 else {
432 bSpecialTag = false;
433 }
434
435 mLocal.SetTag(n, v, bSpecialTag);
436 }
437
438 return true;
439}
440
442{
443 size_t i;
444 TagMap popTagMap;
445
446 // Disable redrawing until we're done
447 mGrid->BeginBatch();
448
449 // Delete all rows
450 if (mGrid->GetNumberRows()) {
451 mGrid->DeleteRows(0, mGrid->GetNumberRows());
452 }
453
454 // Populate the static rows
455 for (i = 0; i < STATICCNT; i++) {
456 mGrid->AppendRows();
457
458 mGrid->SetReadOnly(i, 0);
459 // The special tag name that's displayed and translated may not match
460 // the key string used for internal lookup.
461 mGrid->SetCellValue(i, 0, labelmap[i].label.Translation() );
462 mGrid->SetCellValue(i, 1, mLocal.GetTag(labelmap[i].name));
463
464 if (!mEditTitle &&
465 mGrid->GetCellValue(i, 0).CmpNoCase(LABEL_TITLE.Translation()) == 0) {
466 mGrid->SetReadOnly(i, 1);
467 }
468
469 if (!mEditTrack &&
470 mGrid->GetCellValue(i, 0).CmpNoCase(LABEL_TRACK.Translation()) == 0) {
471 mGrid->SetReadOnly(i, 1);
472 }
473
474 popTagMap[ labelmap[i].name ] = mGrid->GetCellValue(i, 1);
475 }
476
477 // Populate the rest
478 for (const auto &pair : mLocal.GetRange()) {
479 const auto &n = pair.first;
480 const auto &v = pair.second;
481 if (popTagMap.find(n) == popTagMap.end()) {
482 mGrid->AppendRows();
483 mGrid->SetCellValue(i, 0, n);
484 mGrid->SetCellValue(i, 1, v);
485 i++;
486 }
487 }
488
489 // Add an extra one to help with initial sizing and to show it can be done
490 mGrid->AppendRows(1);
491
492 // We're done, so allow the grid to redraw
493 mGrid->EndBatch();
494
495 // Set the editors
496 SetEditors();
497 Layout();
498 Fit();
499
500 return true;
501}
502
503void TagsEditorDialog::OnChange(wxGridEvent & event)
504{
505 static bool ischanging = false;
506
507 // Prevent recursion
508 if (ischanging) {
509 return;
510 }
511
512 event.Skip();
513
514 if (event.GetCol() != 0) {
515 return;
516 }
517
518 // Do not permit duplication of any of the tags.
519 // Tags differing only in case are nondistinct.
520 auto row = event.GetRow();
521 const wxString key0 = mGrid->GetCellValue(row, 0).Upper();
522 auto nn = mGrid->GetNumberRows();
523 for (decltype(nn) ii = 0; ii < nn; ++ii) {
524 if (ii == row)
525 continue;
526 auto key = mGrid->GetCellValue(ii, 0).Upper();
527 if (key0.CmpNoCase(key) == 0) {
528 ischanging = true;
529 wxBell();
530 mGrid->SetGridCursor(ii, 0);
531 event.Veto();
532 ischanging = false;
533 break;
534 }
535 }
536
537 return;
538}
539
540void TagsEditorDialog::OnEdit(wxCommandEvent & WXUNUSED(event))
541{
542 if (mGrid->IsCellEditControlShown()) {
543 mGrid->SaveEditControlValue();
544 mGrid->HideCellEditControl();
545 }
546
547 wxDialogWrapper dlg(this, wxID_ANY, XO("Edit Genres"),
548 wxDefaultPosition, wxDefaultSize,
549 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
550 dlg.SetName();
551 wxTextCtrl *tc;
552
553 ShuttleGui S(&dlg, eIsCreating);
554
555 S.StartVerticalLay(true);
556 {
557 tc = S.AddTextWindow(wxT(""));
558 }
559 S.EndVerticalLay();
560
561 S.AddStandardButtons();
562
563 wxArrayString g;
564 int cnt = mLocal.GetNumUserGenres();
565 for (int i = 0; i < cnt; i++) {
566 g.push_back(mLocal.GetUserGenre(i));
567 }
568 std::sort( g.begin(), g.end() );
569
570 for (int i = 0; i < cnt; i++) {
571 tc->AppendText(g[i] + wxT("\n"));
572 }
573
574 dlg.Center();
575 if (dlg.ShowModal() == wxID_CANCEL) {
576 return;
577 }
578
579 wxFileName fn(FileNames::DataDir(), wxT("genres.txt"));
580 wxFile f(fn.GetFullPath(), wxFile::write);
581 if (!f.IsOpened() || !f.Write(tc->GetValue())) {
583 XO("Unable to save genre file."),
584 XO("Reset Genres") );
585 return;
586 }
587
589
591}
592
593void TagsEditorDialog::OnReset(wxCommandEvent & WXUNUSED(event))
594{
595 int id = AudacityMessageBox(
596 XO("Are you sure you want to reset the genre list to defaults?"),
597 XO("Reset Genres"),
598 wxYES_NO);
599
600 if (id == wxNO) {
601 return;
602 }
604
605 wxFileName fn(FileNames::DataDir(), wxT("genres.txt"));
606 wxTextFile tf(fn.GetFullPath());
607
608 bool open = (tf.Exists() && tf.Open()) ||
609 (!tf.Exists() && tf.Create());
610
611 if (!open) {
613 XO("Unable to open genre file."),
614 XO("Reset Genres") );
616 return;
617 }
618
619 tf.Clear();
620 int cnt = mLocal.GetNumUserGenres();
621 for (int i = 0; i < cnt; i++) {
622 tf.AddLine(mLocal.GetUserGenre(i));
623 }
624
625 if (!tf.Write()) {
627 XO("Unable to save genre file."),
628 XO("Reset Genres") );
630 return;
631 }
632
634
636}
637
638void TagsEditorDialog::OnClear(wxCommandEvent & WXUNUSED(event))
639{
640 mLocal.Clear();
641
643}
644
645void TagsEditorDialog::OnLoad(wxCommandEvent & WXUNUSED(event))
646{
647 wxString fn;
648
649 // Ask the user for the real name
650 fn = SelectFile(FileNames::Operation::_None,
651 XO("Load Metadata As:"),
653 wxT("Tags.xml"),
654 wxT("xml"),
656 wxFD_OPEN | wxRESIZE_BORDER,
657 this);
658
659 // User canceled...
660 if (fn.empty()) {
661 return;
662 }
663
664 // Load the metadata
665 decltype(mLocal) temp;
666 XMLFileReader reader;
667 if (!reader.Parse(&temp, fn)) {
668 // Inform user of load failure
670 reader.GetErrorStr(),
671 XO("Error Loading Metadata"),
672 wxOK | wxCENTRE,
673 this);
674 return;
675 }
676
677 // Remember title and track in case they're read only
678 wxString title = mLocal.GetTag(TAG_TITLE);
679 wxString track = mLocal.GetTag(TAG_TRACK);
680
681 // Replace existing tags with loaded ones
682 mLocal = temp;
683
684 // Restore title
685 if (!mEditTitle) {
687 }
688
689 // Restore track
690 if (!mEditTrack) {
691 mLocal.SetTag(TAG_TRACK, track);
692 }
693
694 // Go fill up the window
696
697 return;
698}
699
700void TagsEditorDialog::OnSave(wxCommandEvent & WXUNUSED(event))
701{
702 wxString fn;
703
704 // Refresh tags
706
707 // Ask the user for the real name
708 fn = SelectFile(FileNames::Operation::_None,
709 XO("Save Metadata As:"),
711 wxT("Tags.xml"),
712 wxT("xml"),
714 wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
715 this);
716
717 // User canceled...
718 if (fn.empty()) {
719 return;
720 }
721
722 GuardedCall( [&] {
723 // Create/Open the file
724 XMLFileWriter writer{ fn, XO("Error Saving Tags File") };
725
726 // Remember title and track in case they're read only
727 wxString title = mLocal.GetTag(TAG_TITLE);
728 wxString track = mLocal.GetTag(TAG_TRACK);
729
730 // Clear title
731 if (!mEditTitle) {
732 mLocal.SetTag(TAG_TITLE, wxEmptyString);
733 }
734
735 // Clear track
736 if (!mEditTrack) {
737 mLocal.SetTag(TAG_TRACK, wxEmptyString);
738 }
739
740 auto cleanup = finally( [&] {
741 // Restore title
742 if (!mEditTitle) {
744 }
745
746 // Restore track
747 if (!mEditTrack) {
748 mLocal.SetTag(TAG_TRACK, track);
749 }
750 } );
751
752 // Write the metadata
753 mLocal.WriteXML(writer);
754
755 writer.Commit();
756 } );
757}
758
759void TagsEditorDialog::OnSaveDefaults(wxCommandEvent & WXUNUSED(event))
760{
761 // Refresh tags
763
764 // Remember title and track in case they're read only
765 wxString title = mLocal.GetTag(TAG_TITLE);
766 wxString track = mLocal.GetTag(TAG_TRACK);
767
768 // Clear title
769 if (!mEditTitle) {
770 mLocal.SetTag(TAG_TITLE, wxEmptyString);
771 }
772
773 // Clear track
774 if (!mEditTrack) {
775 mLocal.SetTag(TAG_TRACK, wxEmptyString);
776 }
777
778 // Remove any previous defaults
779 gPrefs->DeleteGroup(wxT("/Tags"));
780
781 // Write out each tag
782 for (const auto &pair : mLocal.GetRange()) {
783 const auto &n = pair.first;
784 const auto &v = pair.second;
785 gPrefs->Write(wxT("/Tags/") + n, v);
786 }
787 gPrefs->Flush();
788
789 // Restore title
790 if (!mEditTitle) {
792 }
793
794 // Restore track
795 if (!mEditTrack) {
796 mLocal.SetTag(TAG_TRACK, track);
797 }
798}
799
800void TagsEditorDialog::OnAdd(wxCommandEvent & WXUNUSED(event))
801{
802 mGrid->AppendRows();
803}
804
805void TagsEditorDialog::OnRemove(wxCommandEvent & WXUNUSED(event))
806{
807 size_t row = mGrid->GetGridCursorRow();
808
809 if (!mEditTitle &&
810 mGrid->GetCellValue(row, 0).CmpNoCase(LABEL_TITLE.Translation()) == 0) {
811 return;
812 }
813 else if (!mEditTrack &&
814 mGrid->GetCellValue(row, 0)
815 .CmpNoCase(LABEL_TRACK.Translation()) == 0) {
816 return;
817 }
818 else if (row < STATICCNT) {
819 mGrid->SetCellValue(row, 1, wxEmptyString);
820 }
821 else if (row >= STATICCNT) {
822 mGrid->DeleteRows(row, 1);
823 }
824}
825
826void TagsEditorDialog::OnOk(wxCommandEvent & WXUNUSED(event))
827{
828 if (mGrid->IsCellEditControlShown()) {
829 mGrid->SaveEditControlValue();
830 mGrid->HideCellEditControl();
831#if defined(__WXMAC__)
832 // The cell editors do not capture the ENTER key, so it invokes
833 // the default button ("Ok") when it should just close the
834 // editor. So, cancel the "Ok" action.
835 return;
836#endif
837 }
838
839 if (!Validate() || !TransferDataFromWindow()) {
840 return;
841 }
842
843 *mTags = mLocal;
844
845 wxRect r = GetRect();
846 gPrefs->Write(wxT("/TagsEditorDialog/x"), r.x);
847 gPrefs->Write(wxT("/TagsEditorDialog/y"), r.y);
848 gPrefs->Write(wxT("/TagsEditorDialog/width"), r.width);
849 gPrefs->Write(wxT("/TagsEditorDialog/height"), r.height);
850 gPrefs->Flush();
851
852 EndModal(wxID_OK);
853}
854
855void TagsEditorDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
856{
857 DoCancel(false);
858}
859
861{
862 if (mGrid->IsCellEditControlShown()) {
863 auto editor = mGrid->GetCellEditor(mGrid->GetGridCursorRow(),
864 mGrid->GetGridCursorCol());
865 editor->Reset();
866 // To avoid memory leak, don't forget DecRef()!
867 editor->DecRef();
868 mGrid->HideCellEditControl();
869#if defined(__WXMSW__)
870 return;
871#endif
872 }
873
874 auto focus = wxWindow::FindFocus();
875 if (escKey && focus == mGrid)
876 return;
877
878 EndModal(wxID_CANCEL);
879}
880
881void TagsEditorDialog::OnKeyDown(wxKeyEvent &event)
882{
883 if (event.GetKeyCode() == WXK_ESCAPE)
884 DoCancel(true);
885 else
886 event.Skip();
887}
888
890{
891 int cnt = mGrid->GetNumberRows();
892
893 for (int i = 0; i < cnt; i++) {
894 wxString label = mGrid->GetCellValue(i, 0);
895 if (label.CmpNoCase(LABEL_GENRE.Translation()) == 0) {
896 // This use of GetDefaultEditorForType does not require DecRef.
897 mGrid->SetCellEditor(i, 1, mGrid->GetDefaultEditorForType(wxT("Combo")));
898 }
899 else {
900 mGrid->SetCellEditor(i, 1, NULL); //mGrid->GetDefaultEditor());
901 }
902 }
903}
904
906{
907 int cnt = mLocal.GetNumUserGenres();
908 int i;
909 wxString parm;
910 wxArrayString g;
911
912 for (i = 0; i < cnt; i++) {
913 g.push_back(mLocal.GetUserGenre(i));
914 }
915 std::sort( g.begin(), g.end() );
916
917 for (i = 0; i < cnt; i++) {
918 parm = parm + (i == 0 ? wxT("") : wxT(",")) + g[i];
919 }
920
921 // Here was a memory leak! wxWidgets docs for wxGrid::GetDefaultEditorForType() say:
922 // "The caller must call DecRef() on the returned pointer."
923 auto editor = mGrid->GetDefaultEditorForType(wxT("Combo"));
924 editor->SetParameters(parm);
925 editor->DecRef();
926}
927
928bool TagsEditorDialog::IsWindowRectValid(const wxRect *windowRect) const
929{
930 wxDisplay display;
931 wxPoint topLeft(windowRect->GetTopLeft().x, windowRect->GetTopLeft().y);
932 wxPoint topRight(windowRect->GetTopRight().x, windowRect->GetTopRight().y);
933 wxPoint bottomLeft(windowRect->GetBottomLeft().x, windowRect->GetBottomLeft().y);
934 wxPoint bottomRight(windowRect->GetBottomRight().x, windowRect->GetBottomRight().y);
935 display.GetFromPoint(topLeft);
936 if (display.GetFromPoint(topLeft) == -1 &&
937 display.GetFromPoint(topRight) == -1 &&
938 display.GetFromPoint(bottomLeft) == -1 &&
939 display.GetFromPoint(bottomRight) == -1) {
940 return false;
941 }
942
943 return true;
944}
R GuardedCall(const F1 &body, const F2 &handler=F2::Default(), F3 delayedHandler=DefaultDelayedHandlerAction) noexcept(noexcept(handler(std::declval< AudacityException * >())) &&noexcept(handler(nullptr)) &&noexcept(std::function< void(AudacityException *)>{std::move(delayedHandler)}))
Execute some code on any thread; catch any AudacityException; enqueue error report on the main thread...
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
END_EVENT_TABLE()
static const AudacityProject::AttachedObjects::RegisteredFactory key
int min(int a, int b)
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
EffectDistortion::Params params
Definition: Distortion.cpp:83
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
#define _(s)
Definition: Internat.h:75
#define safenew
Definition: MemoryX.h:10
static const auto title
FileConfig * gPrefs
Definition: Prefs.cpp:71
FilePath SelectFile(FileNames::Operation op, const TranslatableString &message, const FilePath &default_path, const FilePath &default_filename, const FileExtension &default_extension, const FileTypes &fileTypes, int flags, wxWindow *parent)
Definition: SelectFile.cpp:17
@ eIsCreating
Definition: ShuttleGui.h:39
@ eOkButton
Definition: ShuttleGui.h:600
@ eCancelButton
Definition: ShuttleGui.h:601
@ eHelpButton
Definition: ShuttleGui.h:604
#define TAG_TRACK
Definition: Tags.h:61
#define TAG_COMMENTS
Definition: Tags.h:64
#define TAG_GENRE
Definition: Tags.h:63
std::unordered_map< wxString, wxString > TagMap
Definition: Tags.h:56
#define TAG_ALBUM
Definition: Tags.h:60
#define TAG_YEAR
Definition: Tags.h:62
#define TAG_TITLE
Definition: Tags.h:58
#define TAG_ARTIST
Definition: Tags.h:59
wxString name
Definition: TagsEditor.cpp:164
#define LABEL_TITLE
Definition: TagsEditor.cpp:144
#define LABEL_COMMENTS
Definition: TagsEditor.cpp:149
TranslatableString label
Definition: TagsEditor.cpp:163
#define STATICCNT
Definition: TagsEditor.cpp:177
#define LABEL_GENRE
Definition: TagsEditor.cpp:148
static TranslatableStrings names
Definition: TagsEditor.cpp:151
#define LABEL_YEAR
Definition: TagsEditor.cpp:147
@ SaveDefaultsID
Definition: TagsEditor.cpp:185
@ DontShowID
Definition: TagsEditor.cpp:188
@ ClearID
Definition: TagsEditor.cpp:180
@ RemoveID
Definition: TagsEditor.cpp:187
@ LoadID
Definition: TagsEditor.cpp:183
@ ResetID
Definition: TagsEditor.cpp:182
@ SaveID
Definition: TagsEditor.cpp:184
@ EditID
Definition: TagsEditor.cpp:181
@ AddID
Definition: TagsEditor.cpp:186
static const struct @82 labelmap[]
#define LABEL_ARTIST
Definition: TagsEditor.cpp:143
#define LABEL_ALBUM
Definition: TagsEditor.cpp:145
#define LABEL_TRACK
Definition: TagsEditor.cpp:146
#define S(N)
Definition: ToChars.cpp:64
std::vector< TranslatableString > TranslatableStrings
static const auto fn
void PaintBackground(wxDC &, const wxRect &WXUNUSED(rectCell), const wxGridCellAttr &WXUNUSED(attr)) override
Definition: TagsEditor.cpp:48
ComboEditor(const wxArrayString &choices, bool allowOthers=false)
Definition: TagsEditor.cpp:41
wxArrayString m_choices
Definition: TagsEditor.cpp:135
virtual void StartingKey(wxKeyEvent &event) override
Definition: TagsEditor.cpp:78
bool m_allowOthers
Definition: TagsEditor.cpp:136
void SetParameters(const wxString &params) override
Definition: TagsEditor.cpp:53
void SetSize(const wxRect &rectOrig) override
Definition: TagsEditor.cpp:64
wxGridCellEditor * Clone() const override
Definition: TagsEditor.cpp:129
virtual bool DeleteGroup(const wxString &key) wxOVERRIDE
Definition: FileConfig.cpp:219
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
FILES_API const FileType XMLFiles
Definition: FileNames.h:74
Supplies an accessible grid based on wxGrid.
Definition: Grid.h:185
static void ShowHelp(wxWindow *parent, const FilePath &localFileName, const URLString &remoteURL, bool bModal=false, bool alwaysDefaultBrowser=false)
Definition: HelpSystem.cpp:237
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:631
Derived from ExpandingToolBar, this dialog allows editing of Tags.
Definition: TagsEditor.h:20
void OnRemove(wxCommandEvent &event)
Definition: TagsEditor.cpp:805
void OnDontShow(wxCommandEvent &Evt)
Definition: TagsEditor.cpp:375
wxGridCellStringRenderer * mStringRenderer
Definition: TagsEditor.h:80
void DoCancel(bool escKey)
Definition: TagsEditor.cpp:860
virtual ~TagsEditorDialog()
Definition: TagsEditor.cpp:271
void OnClear(wxCommandEvent &event)
Definition: TagsEditor.cpp:638
void OnAdd(wxCommandEvent &event)
Definition: TagsEditor.cpp:800
void OnKeyDown(wxKeyEvent &event)
Definition: TagsEditor.cpp:881
bool IsWindowRectValid(const wxRect *windowRect) const
Definition: TagsEditor.cpp:928
void OnReset(wxCommandEvent &event)
Definition: TagsEditor.cpp:593
void PopulateOrExchange(ShuttleGui &S)
Definition: TagsEditor.cpp:284
bool TransferDataToWindow() override
Definition: TagsEditor.cpp:441
void OnOk(wxCommandEvent &event)
Definition: TagsEditor.cpp:826
void OnLoad(wxCommandEvent &event)
Definition: TagsEditor.cpp:645
static AUDACITY_DLL_API bool ShowEditDialog(Tags &tags, wxWindow *parent, const TranslatableString &title, bool force=false)
Definition: TagsEditor.cpp:23
void OnSave(wxCommandEvent &event)
Definition: TagsEditor.cpp:700
bool TransferDataFromWindow() override
Definition: TagsEditor.cpp:387
void OnHelp(wxCommandEvent &Evt)
Definition: TagsEditor.cpp:382
void OnSaveDefaults(wxCommandEvent &event)
Definition: TagsEditor.cpp:759
void OnChange(wxGridEvent &event)
Definition: TagsEditor.cpp:503
ComboEditor * mComboEditor
Definition: TagsEditor.h:79
void OnEdit(wxCommandEvent &event)
Definition: TagsEditor.cpp:540
void OnCancel(wxCommandEvent &event)
Definition: TagsEditor.cpp:855
ID3 Tags (for MP3)
Definition: Tags.h:73
void Clear()
Definition: Tags.cpp:308
void WriteXML(XMLWriter &xmlFile) const
Definition: Tags.cpp:549
Iterators GetRange() const
Definition: Tags.cpp:436
wxString GetUserGenre(int value)
Definition: Tags.cpp:374
void LoadDefaultGenres()
Definition: Tags.cpp:348
int GetNumUserGenres()
Definition: Tags.cpp:343
void SetTag(const wxString &name, const wxString &value, const bool bSpecialTag=false)
Definition: Tags.cpp:441
void LoadGenres()
Definition: Tags.cpp:356
wxString GetTag(const wxString &name) const
Definition: Tags.cpp:416
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
Reads a file and passes the results through an XMLTagHandler.
Definition: XMLFileReader.h:19
const TranslatableString & GetErrorStr() const
bool Parse(XMLTagHandler *baseHandler, const FilePath &fname)
Wrapper to output XML data to files.
Definition: XMLWriter.h:84
void SetName(const TranslatableString &title)
FILES_API FilePath DataDir()
Audacity user data directory.