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