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