Audacity  3.0.3
Tags.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  Tags.cpp
6 
7  Dominic Mazzoni
8 
9 *******************************************************************//****************************************************************//*******************************************************************/
32 
33 
34 #include "Tags.h"
35 
36 // For compilers that support precompilation, includes "wx/wx.h".
37 #include <wx/wxprec.h>
38 
39 #include <wx/setup.h> // for wxUSE_* macros
40 
41 #ifndef WX_PRECOMP
42 // Include your minimal set of headers here, or wx.h
43 #include <wx/window.h>
44 #endif
45 
46 #include "FileNames.h"
47 #include "Prefs.h"
48 #include "Project.h"
49 #include "ProjectFileIORegistry.h"
50 #include "ShuttleGui.h"
51 #include "widgets/Grid.h"
53 #include "widgets/HelpSystem.h"
54 #include "xml/XMLFileReader.h"
55 
56 #include <wx/button.h>
57 #include <wx/choice.h>
58 #include <wx/filename.h>
59 #include <wx/intl.h>
60 #include <wx/listctrl.h>
61 #include <wx/notebook.h>
62 #include <wx/radiobut.h>
63 #include <wx/scrolbar.h>
64 #include <wx/sizer.h>
65 #include <wx/stattext.h>
66 #include <wx/string.h>
67 #include <wx/textctrl.h>
68 #include <wx/textfile.h>
69 #include <wx/combobox.h>
70 #include <wx/display.h>
71 
72 static const wxChar *DefaultGenres[] =
73 {
74  wxT("Blues"),
75  wxT("Classic Rock"),
76  wxT("Country"),
77  wxT("Dance"),
78  wxT("Disco"),
79  wxT("Funk"),
80  wxT("Grunge"),
81  wxT("Hip-Hop"),
82  wxT("Jazz"),
83  wxT("Metal"),
84  wxT("New Age"),
85  wxT("Oldies"),
86  wxT("Other"),
87  wxT("Pop"),
88  wxT("R&B"),
89  wxT("Rap"),
90  wxT("Reggae"),
91  wxT("Rock"),
92  wxT("Techno"),
93  wxT("Industrial"),
94  wxT("Alternative"),
95  wxT("Ska"),
96  wxT("Death Metal"),
97  wxT("Pranks"),
98  wxT("Soundtrack"),
99  wxT("Euro-Techno"),
100  wxT("Ambient"),
101  wxT("Trip-Hop"),
102  wxT("Vocal"),
103  wxT("Jazz+Funk"),
104  wxT("Fusion"),
105  wxT("Trance"),
106  wxT("Classical"),
107  wxT("Instrumental"),
108  wxT("Acid"),
109  wxT("House"),
110  wxT("Game"),
111  wxT("Sound Clip"),
112  wxT("Gospel"),
113  wxT("Noise"),
114  wxT("Alt. Rock"),
115  wxT("Bass"),
116  wxT("Soul"),
117  wxT("Punk"),
118  wxT("Space"),
119  wxT("Meditative"),
120  wxT("Instrumental Pop"),
121  wxT("Instrumental Rock"),
122  wxT("Ethnic"),
123  wxT("Gothic"),
124  wxT("Darkwave"),
125  wxT("Techno-Industrial"),
126  wxT("Electronic"),
127  wxT("Pop-Folk"),
128  wxT("Eurodance"),
129  wxT("Dream"),
130  wxT("Southern Rock"),
131  wxT("Comedy"),
132  wxT("Cult"),
133  wxT("Gangsta Rap"),
134  wxT("Top 40"),
135  wxT("Christian Rap"),
136  wxT("Pop/Funk"),
137  wxT("Jungle"),
138  wxT("Native American"),
139  wxT("Cabaret"),
140  wxT("New Wave"),
141  wxT("Psychedelic"),
142  wxT("Rave"),
143  wxT("Showtunes"),
144  wxT("Trailer"),
145  wxT("Lo-Fi"),
146  wxT("Tribal"),
147  wxT("Acid Punk"),
148  wxT("Acid Jazz"),
149  wxT("Polka"),
150  wxT("Retro"),
151  wxT("Musical"),
152  wxT("Rock & Roll"),
153  wxT("Hard Rock"),
154  wxT("Folk"),
155  wxT("Folk/Rock"),
156  wxT("National Folk"),
157  wxT("Swing"),
158  wxT("Fast-Fusion"),
159  wxT("Bebob"),
160  wxT("Latin"),
161  wxT("Revival"),
162  wxT("Celtic"),
163  wxT("Bluegrass"),
164  wxT("Avantgarde"),
165  wxT("Gothic Rock"),
166  wxT("Progressive Rock"),
167  wxT("Psychedelic Rock"),
168  wxT("Symphonic Rock"),
169  wxT("Slow Rock"),
170  wxT("Big Band"),
171  wxT("Chorus"),
172  wxT("Easy Listening"),
173  wxT("Acoustic"),
174  wxT("Humour"),
175  wxT("Speech"),
176  wxT("Chanson"),
177  wxT("Opera"),
178  wxT("Chamber Music"),
179  wxT("Sonata"),
180  wxT("Symphony"),
181  wxT("Booty Bass"),
182  wxT("Primus"),
183  wxT("Porn Groove"),
184  wxT("Satire"),
185  wxT("Slow Jam"),
186  wxT("Club"),
187  wxT("Tango"),
188  wxT("Samba"),
189  wxT("Folklore"),
190  wxT("Ballad"),
191  wxT("Power Ballad"),
192  wxT("Rhythmic Soul"),
193  wxT("Freestyle"),
194  wxT("Duet"),
195  wxT("Punk Rock"),
196  wxT("Drum Solo"),
197  wxT("A Cappella"),
198  wxT("Euro-House"),
199  wxT("Dance Hall"),
200  wxT("Goa"),
201  wxT("Drum & Bass"),
202  wxT("Club-House"),
203  wxT("Hardcore"),
204  wxT("Terror"),
205  wxT("Indie"),
206  wxT("BritPop"),
207 
208  // Standard name is offensive (see "http://www.audacityteam.org/forum/viewtopic.php?f=11&t=3924").
209  wxT("Offensive"), // wxT("Negerpunk"),
210 
211  wxT("Polsk Punk"),
212  wxT("Beat"),
213  wxT("Christian Gangsta Rap"),
214  wxT("Heavy Metal"),
215  wxT("Black Metal"),
216  wxT("Crossover"),
217  wxT("Contemporary Christian"),
218  wxT("Christian Rock"),
219  wxT("Merengue"),
220  wxT("Salsa"),
221  wxT("Thrash Metal"),
222  wxT("Anime"),
223  wxT("JPop"),
224  wxT("Synthpop")
225 };
226 
228  wxT( "tags" ),
229  []( AudacityProject &project ){ return &Tags::Get( project ); }
230 };
231 
233  [](AudacityProject &){ return std::make_shared< Tags >(); }
234 };
235 
237 {
238  return project.AttachedObjects::Get< Tags >( key );
239 }
240 
241 const Tags &Tags::Get( const AudacityProject &project )
242 {
243  return Get( const_cast< AudacityProject & >( project ) );
244 }
245 
246 Tags &Tags::Set( AudacityProject &project, const std::shared_ptr< Tags > &tags )
247 {
248  auto &result = *tags;
249  project.AttachedObjects::Assign( key, tags );
250  return result;
251 }
252 
254 {
255  mEditTitle = true;
256  mEditTrackNumber = true;
257 
258  LoadDefaults();
259  LoadGenres();
260 }
261 
263 {
264 }
265 
266 std::shared_ptr<Tags> Tags::Duplicate() const
267 {
268  return std::make_shared<Tags>(*this);
269 }
270 
271 void Tags::Merge( const Tags &other )
272 {
273  for ( auto &pair : other.mMap ) {
274  SetTag( pair.first, pair.second );
275  }
276 }
277 
278 Tags & Tags::operator=(const Tags & src)
279 {
280  mEditTitle = src.mEditTitle;
282 
283  mXref.clear();
284  mXref = src.mXref;
285  mMap.clear();
286  mMap = src.mMap;
287 
288  mGenres.clear();
289  mGenres = src.mGenres;
290 
291  return *this;
292 }
293 
295 {
296  wxString path;
297  wxString name;
298  wxString value;
299  long ndx;
300  bool cont;
301 
302  // Set the parent group
303  path = gPrefs->GetPath();
304  gPrefs->SetPath(wxT("/Tags"));
305 
306  // Process all entries in the group
307  cont = gPrefs->GetFirstEntry(name, ndx);
308  while (cont) {
309  gPrefs->Read(name, &value, wxT(""));
310 
311  if (name == wxT("ID3V2")) {
312  // LLL: This is obsolute, but it must be handled and ignored.
313  }
314  else {
315  SetTag(name, value);
316  }
317 
318  cont = gPrefs->GetNextEntry(name, ndx);
319  }
320 
321  // Restore original group
322  gPrefs->SetPath(path);
323 }
324 
326 {
327  // At least one of these should be filled in, otherwise
328  // it's assumed that the tags have not been set...
330  return false;
331  }
332 
333  return true;
334 }
335 
337 {
338  mXref.clear();
339  mMap.clear();
340 }
341 
342 namespace {
343  bool EqualMaps(const TagMap &map1, const TagMap &map2)
344  {
345  // Maps are unordered, hash maps; can't just iterate in tandem and
346  // compare.
347  if (map1.size() != map2.size())
348  return false;
349 
350  for (const auto &pair : map2) {
351  auto iter = map1.find(pair.first);
352  if (iter == map1.end() || iter->second != pair.second)
353  return false;
354  }
355 
356  return true;
357  }
358 }
359 
360 bool operator== (const Tags &lhs, const Tags &rhs)
361 {
362  if (!EqualMaps(lhs.mXref, rhs.mXref))
363  return false;
364 
365  if (!EqualMaps(lhs.mMap, rhs.mMap))
366  return false;
367 
368  return
369  lhs.mGenres == rhs.mGenres
370  &&
371  lhs.mEditTitle == rhs.mEditTitle
372  &&
374  ;
375 }
376 
377 void Tags::AllowEditTitle(bool editTitle)
378 {
379  mEditTitle = editTitle;
380 }
381 
382 void Tags::AllowEditTrackNumber(bool editTrackNumber)
383 {
384  mEditTrackNumber = editTrackNumber;
385 }
386 
388 {
389  return mGenres.size();
390 }
391 
393 {
394  mGenres.clear();
395  for (size_t i = 0; i < WXSIZEOF(DefaultGenres); i++) {
396  mGenres.push_back(DefaultGenres[i]);
397  }
398 }
399 
401 {
402  wxFileName fn(FileNames::DataDir(), wxT("genres.txt"));
403  wxTextFile tf(fn.GetFullPath());
404 
405  if (!tf.Exists() || !tf.Open()) {
407  return;
408  }
409 
410  mGenres.clear();
411 
412  int cnt = tf.GetLineCount();
413  for (int i = 0; i < cnt; i++) {
414  mGenres.push_back(tf.GetLine(i));
415  }
416 }
417 
418 wxString Tags::GetUserGenre(int i)
419 {
420  if (i >= 0 && i < GetNumUserGenres()) {
421  return mGenres[i];
422  }
423 
424  return wxT("");
425 }
426 
427 wxString Tags::GetGenre(int i)
428 {
429  int cnt = WXSIZEOF(DefaultGenres);
430 
431  if (i >= 0 && i < cnt) {
432  return DefaultGenres[i];
433  }
434 
435  return wxT("");
436 }
437 
438 int Tags::GetGenre(const wxString & name)
439 {
440  int cnt = WXSIZEOF(DefaultGenres);
441 
442  for (int i = 0; i < cnt; i++) {
443  if (name.CmpNoCase(DefaultGenres[i])) {
444  return i;
445  }
446  }
447 
448  return 255;
449 }
450 
451 bool Tags::HasTag(const wxString & name) const
452 {
453  wxString key = name;
454  key.UpperCase();
455 
456  auto iter = mXref.find(key);
457  return (iter != mXref.end());
458 }
459 
460 wxString Tags::GetTag(const wxString & name) const
461 {
462  wxString key = name;
463  key.UpperCase();
464 
465  auto iter = mXref.find(key);
466 
467  if (iter == mXref.end()) {
468  return wxEmptyString;
469  }
470 
471  auto iter2 = mMap.find(iter->second);
472  if (iter2 == mMap.end()) {
473  wxASSERT(false);
474  return wxEmptyString;
475  }
476  else
477  return iter2->second;
478 }
479 
481 {
482  return { mMap.begin(), mMap.end() };
483 }
484 
485 void Tags::SetTag(const wxString & name, const wxString & value, const bool bSpecialTag)
486 {
487  // We don't like empty names
488  if (name.empty()) {
489  return;
490  }
491 
492  // Tag name must be ascii
493  if (!name.IsAscii()) {
494  wxLogError("Tag rejected (Non-ascii character in name)");
495  return;
496  }
497 
498  // All keys are uppercase
499  wxString key = name;
500  key.UpperCase();
501 
502  // Look it up
503  TagMap::iterator iter = mXref.find(key);
504 
505  // The special tags, if empty, should not exist.
506  // However it is allowable for a custom tag to be empty.
507  // See Bug 440 and Bug 1382
508  if (value.empty() && bSpecialTag) {
509  // Erase the tag
510  if (iter == mXref.end())
511  // nothing to do
512  ;
513  else {
514  mMap.erase(iter->second);
515  mXref.erase(iter);
516  }
517  }
518  else
519  {
520  if (iter == mXref.end()) {
521  // Didn't find the tag
522 
523  // Add a NEW tag
524  mXref[key] = name;
525  mMap[name] = value;
526  }
527  else if (iter->second != name) {
528  // Watch out for case differences!
529  mMap[name] = value;
530  mMap.erase(iter->second);
531  iter->second = name;
532  }
533  else {
534  // Update the value
535  mMap[iter->second] = value;
536  }
537  }
538 }
539 
540 void Tags::SetTag(const wxString & name, const int & value)
541 {
542  SetTag(name, wxString::Format(wxT("%d"), value));
543 }
544 
545 bool Tags::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
546 {
547  if (wxStrcmp(tag, wxT("tags")) == 0) {
548  return true;
549  }
550 
551  if (wxStrcmp(tag, wxT("tag")) == 0) {
552  wxString n, v;
553 
554  while (*attrs) {
555  wxString attr = *attrs++;
556  if (attr.empty())
557  break;
558  wxString value = *attrs++;
559 
560  if (!XMLValueChecker::IsGoodString(attr) ||
562  break;
563  }
564 
565  if (attr == wxT("name")) {
566  n = value;
567  }
568  else if (attr == wxT("value")) {
569  v = value;
570  }
571  }
572 
573  if (n == wxT("id3v2")) {
574  // LLL: This is obsolete, but it must be handled and ignored.
575  }
576  else {
577  SetTag(n, v);
578  }
579 
580  return true;
581  }
582 
583  return false;
584 }
585 
587 {
588  if (wxStrcmp(tag, wxT("tags")) == 0) {
589  return this;
590  }
591 
592  if (wxStrcmp(tag, wxT("tag")) == 0) {
593  return this;
594  }
595 
596  return NULL;
597 }
598 
599 void Tags::WriteXML(XMLWriter &xmlFile) const
600 // may throw
601 {
602  xmlFile.StartTag(wxT("tags"));
603 
604  for (const auto &pair : GetRange()) {
605  const auto &n = pair.first;
606  const auto &v = pair.second;
607  xmlFile.StartTag(wxT("tag"));
608  xmlFile.WriteAttr(wxT("name"), n);
609  xmlFile.WriteAttr(wxT("value"), v);
610  xmlFile.EndTag(wxT("tag"));
611  }
612 
613  xmlFile.EndTag(wxT("tags"));
614 }
615 
616 bool Tags::ShowEditDialog(wxWindow *parent, const TranslatableString &title, bool force)
617 {
618  if (force) {
619  TagsEditorDialog dlg(parent, title, this, mEditTitle, mEditTrackNumber);
620 
621  return dlg.ShowModal() == wxID_OK;
622  }
623 
624  return true;
625 }
626 //
627 // ComboEditor - Wrapper to prevent unwanted background erasure
628 //
629 
630 class ComboEditor final : public wxGridCellChoiceEditor
631 {
632 public:
633  ComboEditor(const wxArrayString& choices, bool allowOthers = false)
634  : wxGridCellChoiceEditor(choices, allowOthers)
635  , m_choices{ choices }
636  , m_allowOthers{ allowOthers }
637  {
638  }
639 
640  void PaintBackground(wxDC&, const wxRect& WXUNUSED(rectCell), const wxGridCellAttr & WXUNUSED(attr)) override
641  {
642  // Ignore it (a must on the Mac as the erasure causes problems.)
643  }
644 
645  void SetParameters(const wxString& params) override
646  {
647  wxGridCellChoiceEditor::SetParameters(params);
648 
649  // Refresh the wxComboBox with NEW values
650  if (Combo()) {
651  Combo()->Clear();
652  Combo()->Append(m_choices);
653  }
654  }
655 
656  void SetSize(const wxRect& rectOrig) override
657  {
658  wxRect rect(rectOrig);
659  wxRect r = Combo()->GetRect();
660 
661  // Center the combo box in or over the cell
662  rect.y -= (r.GetHeight() - rect.GetHeight()) / 2;
663  rect.height = r.GetHeight();
664 
665  wxGridCellChoiceEditor::SetSize(rect);
666  }
667 
668  // Fix for Bug 1389
669  // July 2016: ANSWER-ME: Does this need reporting upstream to wxWidgets?
670  virtual void StartingKey(wxKeyEvent& event) override
671  {
672  // Lifted from wxGridCellTextEditor and adapted to combo.
673 
674  // [Below is comment from wxWidgets code]
675  // Since this is now happening in the EVT_CHAR event EmulateKeyPress is no
676  // longer an appropriate way to get the character into the text control.
677  // Do it ourselves instead. We know that if we get this far that we have
678  // a valid character, so not a whole lot of testing needs to be done.
679 
680  //The only difference to wxGridCellTextEditor.
681  //wxTextCtrl* tc = (wxTextCtrl *)m_control;
682  wxComboBox * tc = Combo();
683  int ch;
684 
685  bool isPrintable;
686 
687  #if wxUSE_UNICODE
688  ch = event.GetUnicodeKey();
689  if ( ch != WXK_NONE )
690  isPrintable = true;
691  else
692  #endif // wxUSE_UNICODE
693  {
694  ch = event.GetKeyCode();
695  isPrintable = ch >= WXK_SPACE && ch < WXK_START;
696  }
697 
698  switch (ch)
699  {
700  case WXK_DELETE:
701  // Delete the initial character when starting to edit with DELETE.
702  tc->Remove(0, 1);
703  break;
704 
705  case WXK_BACK:
706  // Delete the last character when starting to edit with BACKSPACE.
707  {
708  const long pos = tc->GetLastPosition();
709  tc->Remove(pos - 1, pos);
710  }
711  break;
712 
713  default:
714  if ( isPrintable )
715  tc->WriteText(static_cast<wxChar>(ch));
716  break;
717  }
718  }
719 
720  // Clone is required by wxwidgets; implemented via copy constructor
721  wxGridCellEditor *Clone() const override
722  {
724  }
725 
726 private:
727  wxArrayString m_choices;
729 };
730 
731 //
732 // Editor
733 //
734 
735 #define LABEL_ARTIST XO("Artist Name")
736 #define LABEL_TITLE XO("Track Title")
737 #define LABEL_ALBUM XO("Album Title")
738 #define LABEL_TRACK XO("Track Number")
739 #define LABEL_YEAR XO("Year")
740 #define LABEL_GENRE XO("Genre")
741 #define LABEL_COMMENTS XO("Comments")
742 
744  LABEL_ARTIST,
745  LABEL_TITLE,
746  LABEL_ALBUM,
747  LABEL_TRACK,
748  LABEL_YEAR,
749  LABEL_GENRE,
751 };
752 
753 static const struct
754 {
756  wxString name;
757 }
758 labelmap[] =
759 {
761  { LABEL_TITLE, TAG_TITLE },
762  { LABEL_ALBUM, TAG_ALBUM },
763  { LABEL_TRACK, TAG_TRACK },
764  { LABEL_YEAR, TAG_YEAR },
765  { LABEL_GENRE, TAG_GENRE },
767 };
768 
769 #define STATICCNT WXSIZEOF(labelmap)
770 
771 enum {
772  ClearID = 10000,
780  DontShowID
781 };
782 
783 BEGIN_EVENT_TABLE(TagsEditorDialog, wxDialogWrapper)
784  EVT_GRID_CELL_CHANGED(TagsEditorDialog::OnChange)
797  EVT_KEY_DOWN(TagsEditorDialog::OnKeyDown)
799 
801  const TranslatableString &title,
802  Tags * tags,
803  bool editTitle,
804  bool editTrack)
805 : wxDialogWrapper(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize,
806  wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
807  mTags(tags),
808  mEditTitle(editTitle),
809  mEditTrack(editTrack)
810 {
811  SetName();
812 
813  mGrid = NULL;
814 
815  // Make a local copy of the passed in tags
816  mLocal = *mTags;
817 
818  // Build, size, and position the dialog
819  ShuttleGui S(this, eIsCreating);
820  PopulateOrExchange(S);
821 
822  TransferDataToWindow();
823 
824  Layout();
825  Fit();
826  Center();
827  SetSizeHints(GetSize());
828 
829  // Restore the original tags because TransferDataToWindow() will be called again
830  mLocal.Clear();
831  mLocal = *mTags;
832 
833  // Override size and position with last saved
834  wxRect r = GetRect();
835  gPrefs->Read(wxT("/TagsEditorDialog/x"), &r.x, r.x);
836  gPrefs->Read(wxT("/TagsEditorDialog/y"), &r.y, r.y);
837  gPrefs->Read(wxT("/TagsEditorDialog/width"), &r.width, r.width);
838  gPrefs->Read(wxT("/TagsEditorDialog/height"), &r.height, r.height);
839  //On multi-monitor systems, there's a chance the last saved window position is
840  //on a monitor that has been removed or is unavailable.
841  if (IsWindowRectValid(&r))
842  Move(r.GetPosition());
843 
844  SetSize(r.GetSize());
845  Layout();
846 
847  // Resize value column width based on width of columns and the vertical scrollbar
848  wxScrollBar sb(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL);
849  r = mGrid->GetClientRect();
850  r.width -= mGrid->GetColSize(0);
851  r.width -= sb.GetSize().GetWidth();
852  r.width -= 10;
853  r.width -= r.x;
854  mGrid->SetColSize(1, r.width);
855  //Bug 2038
856  mGrid->SetFocus();
857 
858  // Load the genres
859  PopulateGenres();
860 }
861 
863 {
864  // This DELETE is not needed because wxWidgets owns the grid.
865 // DELETE mGrid;
866 
867 // TODO: Need to figure out if these should be deleted. Looks like the wxGrid
868 // code takes ownership and uses reference counting, but there's been
869 // cases where they show up as memory leaks.
870 // PRL: Fixed the leaks, see commit c87eb0804bc5f40659b133cab6e2ade061959645
871 // DELETE mStringRenderer;
872 // DELETE mComboEditor;
873 }
874 
876 {
877  bool bShow;
878  gPrefs->Read(wxT("/AudioFiles/ShowId3Dialog"), &bShow, true );
879 
880  S.StartVerticalLay();
881  {
882  S.StartHorizontalLay(wxALIGN_LEFT, 0);
883  {
884  S.AddUnits(XO("Use arrow keys (or ENTER key after editing) to navigate fields."));
885  }
886  S.EndHorizontalLay();
887 
888  if (mGrid == NULL) {
889  mGrid = safenew Grid(S.GetParent(),
890  wxID_ANY,
891  wxDefaultPosition,
892  wxDefaultSize,
893  wxSUNKEN_BORDER);
894 
895  mGrid->RegisterDataType(wxT("Combo"),
896  (mStringRenderer = safenew wxGridCellStringRenderer),
897  (mComboEditor = safenew ComboEditor(wxArrayString(), true)));
898 
899  mGrid->SetColLabelSize(mGrid->GetDefaultRowSize());
900 
901  auto cs = transform_container<wxArrayStringEx>(
902  names, std::mem_fn( &TranslatableString::Translation ) );
903 
904  // Build the initial (empty) grid
905  mGrid->CreateGrid(0, 2, wxGrid::wxGridSelectRows);
906  mGrid->SetRowLabelSize(0);
907  mGrid->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTER);
908  mGrid->SetColLabelValue(0, _("Tag"));
909  mGrid->SetColLabelValue(1, _("Value"));
910 
911  // Resize the name column and set default row height.
912  wxComboBox tc(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, cs);
913  mGrid->SetColSize(0, tc.GetSize().x);
914  mGrid->SetColMinimalWidth(0, tc.GetSize().x);
915  }
916  S.Prop(1)
917  .Position(wxEXPAND | wxALL)
918  .AddWindow(mGrid);
919 
920  S.StartMultiColumn(4, wxALIGN_CENTER);
921  {
922  S.Id(AddID).AddButton(XXO("&Add"));
923  S.Id(RemoveID).AddButton(XXO("&Remove"));
924  S.AddTitle( {} );
925  S.Id(ClearID).AddButton(XXO("Cl&ear"));
926  }
927  S.EndMultiColumn();
928 
929  S.StartHorizontalLay(wxALIGN_CENTRE, 0);
930  {
931  S.StartStatic(XO("Genres"));
932  {
933  S.StartMultiColumn(4, wxALIGN_CENTER);
934  {
935  S.Id(EditID).AddButton(XXO("E&dit..."));
936  S.Id(ResetID).AddButton(XXO("Rese&t..."));
937  }
938  S.EndMultiColumn();
939  }
940  S.EndStatic();
941  S.StartStatic(XO("Template"));
942  {
943  S.StartMultiColumn(4, wxALIGN_CENTER);
944  {
945  S.Id(LoadID).AddButton(XXO("&Load..."));
946  S.Id(SaveID).AddButton(XXO("&Save..."));
947  S.AddTitle( {} );
948  S.Id(SaveDefaultsID).AddButton(XXO("Set De&fault"));
949  }
950  S.EndMultiColumn();
951  }
952  S.EndStatic();
953  }
954  S.EndHorizontalLay();
955  S.StartHorizontalLay(wxALIGN_LEFT, 0);
956  {
957  S.Id( DontShowID ).AddCheckBox( XXO("Don't show this when exporting audio"), !bShow );
958  }
959  S.EndHorizontalLay();
960  }
961  S.EndVerticalLay();
962 
964 }
965 
966 void TagsEditorDialog::OnDontShow( wxCommandEvent & Evt )
967 {
968  bool bShow = !Evt.IsChecked();
969  gPrefs->Write(wxT("/AudioFiles/ShowId3Dialog"), bShow );
970  gPrefs->Flush();
971 }
972 
973 void TagsEditorDialog::OnHelp(wxCommandEvent& WXUNUSED(event))
974 {
975  HelpSystem::ShowHelp(this, wxT("Metadata_Editor"), true);
976 }
977 
979 {
980  int i, cnt = mGrid->GetNumberRows();
981 
982  if (mGrid->IsCellEditControlShown()) {
983  mGrid->SaveEditControlValue();
984  mGrid->HideCellEditControl();
985  }
986 
987  mLocal.Clear();
988  for (i = 0; i < cnt; i++) {
989  // Get tag name from the grid
990 
991  auto n = mGrid->GetCellValue(i, 0);
992  wxString v = mGrid->GetCellValue(i, 1);
993 
994  if (n.empty()) {
995  continue;
996  }
997 
998  bool bSpecialTag = true;
999 
1000  // Map special tag names back to internal keys
1001  if (n.CmpNoCase(LABEL_ARTIST.Translation()) == 0) {
1002  n = TAG_ARTIST;
1003  }
1004  else if (n.CmpNoCase(LABEL_TITLE.Translation()) == 0) {
1005  n = TAG_TITLE;
1006  }
1007  else if (n.CmpNoCase(LABEL_ALBUM.Translation()) == 0) {
1008  n = TAG_ALBUM;
1009  }
1010  else if (n.CmpNoCase(LABEL_TRACK.Translation()) == 0) {
1011  n = TAG_TRACK;
1012  }
1013  else if (n.CmpNoCase(LABEL_YEAR.Translation()) == 0) {
1014  n = TAG_YEAR;
1015  }
1016  else if (n.CmpNoCase(LABEL_GENRE.Translation()) == 0) {
1017  n = TAG_GENRE;
1018  }
1019  else if (n.CmpNoCase(LABEL_COMMENTS.Translation()) == 0) {
1020  n = TAG_COMMENTS;
1021  }
1022  else {
1023  bSpecialTag = false;
1024  }
1025 
1026  mLocal.SetTag(n, v, bSpecialTag);
1027  }
1028 
1029  return true;
1030 }
1031 
1033 {
1034  size_t i;
1035  TagMap popTagMap;
1036 
1037  // Disable redrawing until we're done
1038  mGrid->BeginBatch();
1039 
1040  // Delete all rows
1041  if (mGrid->GetNumberRows()) {
1042  mGrid->DeleteRows(0, mGrid->GetNumberRows());
1043  }
1044 
1045  // Populate the static rows
1046  for (i = 0; i < STATICCNT; i++) {
1047  mGrid->AppendRows();
1048 
1049  mGrid->SetReadOnly(i, 0);
1050  // The special tag name that's displayed and translated may not match
1051  // the key string used for internal lookup.
1052  mGrid->SetCellValue(i, 0, labelmap[i].label.Translation() );
1053  mGrid->SetCellValue(i, 1, mLocal.GetTag(labelmap[i].name));
1054 
1055  if (!mEditTitle &&
1056  mGrid->GetCellValue(i, 0).CmpNoCase(LABEL_TITLE.Translation()) == 0) {
1057  mGrid->SetReadOnly(i, 1);
1058  }
1059 
1060  if (!mEditTrack &&
1061  mGrid->GetCellValue(i, 0).CmpNoCase(LABEL_TRACK.Translation()) == 0) {
1062  mGrid->SetReadOnly(i, 1);
1063  }
1064 
1065  popTagMap[ labelmap[i].name ] = mGrid->GetCellValue(i, 1);
1066  }
1067 
1068  // Populate the rest
1069  for (const auto &pair : mLocal.GetRange()) {
1070  const auto &n = pair.first;
1071  const auto &v = pair.second;
1072  if (popTagMap.find(n) == popTagMap.end()) {
1073  mGrid->AppendRows();
1074  mGrid->SetCellValue(i, 0, n);
1075  mGrid->SetCellValue(i, 1, v);
1076  i++;
1077  }
1078  }
1079 
1080  // Add an extra one to help with initial sizing and to show it can be done
1081  mGrid->AppendRows(1);
1082 
1083  // We're done, so allow the grid to redraw
1084  mGrid->EndBatch();
1085 
1086  // Set the editors
1087  SetEditors();
1088  Layout();
1089  Fit();
1090 
1091  return true;
1092 }
1093 
1094 void TagsEditorDialog::OnChange(wxGridEvent & event)
1095 {
1096  static bool ischanging = false;
1097 
1098  // Prevent recursion
1099  if (ischanging) {
1100  return;
1101  }
1102 
1103  event.Skip();
1104 
1105  if (event.GetCol() != 0) {
1106  return;
1107  }
1108 
1109  // Do not permit duplication of any of the tags.
1110  // Tags differing only in case are nondistinct.
1111  auto row = event.GetRow();
1112  const wxString key0 = mGrid->GetCellValue(row, 0).Upper();
1113  auto nn = mGrid->GetNumberRows();
1114  for (decltype(nn) ii = 0; ii < nn; ++ii) {
1115  if (ii == row)
1116  continue;
1117  auto key = mGrid->GetCellValue(ii, 0).Upper();
1118  if (key0.CmpNoCase(key) == 0) {
1119  ischanging = true;
1120  wxBell();
1121  mGrid->SetGridCursor(ii, 0);
1122  event.Veto();
1123  ischanging = false;
1124  break;
1125  }
1126  }
1127 
1128  return;
1129 }
1130 
1131 void TagsEditorDialog::OnEdit(wxCommandEvent & WXUNUSED(event))
1132 {
1133  if (mGrid->IsCellEditControlShown()) {
1134  mGrid->SaveEditControlValue();
1135  mGrid->HideCellEditControl();
1136  }
1137 
1138  wxDialogWrapper dlg(this, wxID_ANY, XO("Edit Genres"),
1139  wxDefaultPosition, wxDefaultSize,
1140  wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
1141  dlg.SetName();
1142  wxTextCtrl *tc;
1143 
1144  ShuttleGui S(&dlg, eIsCreating);
1145 
1146  S.StartVerticalLay(true);
1147  {
1148  tc = S.AddTextWindow(wxT(""));
1149  }
1150  S.EndVerticalLay();
1151 
1152  S.AddStandardButtons();
1153 
1154  wxArrayString g;
1155  int cnt = mLocal.GetNumUserGenres();
1156  for (int i = 0; i < cnt; i++) {
1157  g.push_back(mLocal.GetUserGenre(i));
1158  }
1159  std::sort( g.begin(), g.end() );
1160 
1161  for (int i = 0; i < cnt; i++) {
1162  tc->AppendText(g[i] + wxT("\n"));
1163  }
1164 
1165  dlg.Center();
1166  if (dlg.ShowModal() == wxID_CANCEL) {
1167  return;
1168  }
1169 
1170  wxFileName fn(FileNames::DataDir(), wxT("genres.txt"));
1171  wxFile f(fn.GetFullPath(), wxFile::write);
1172  if (!f.IsOpened() || !f.Write(tc->GetValue())) {
1174  XO("Unable to save genre file."),
1175  XO("Reset Genres") );
1176  return;
1177  }
1178 
1179  mLocal.LoadGenres();
1180 
1181  PopulateGenres();
1182 }
1183 
1184 void TagsEditorDialog::OnReset(wxCommandEvent & WXUNUSED(event))
1185 {
1186  int id = AudacityMessageBox(
1187  XO("Are you sure you want to reset the genre list to defaults?"),
1188  XO("Reset Genres"),
1189  wxYES_NO);
1190 
1191  if (id == wxNO) {
1192  return;
1193  }
1195 
1196  wxFileName fn(FileNames::DataDir(), wxT("genres.txt"));
1197  wxTextFile tf(fn.GetFullPath());
1198 
1199  bool open = (tf.Exists() && tf.Open()) ||
1200  (!tf.Exists() && tf.Create());
1201 
1202  if (!open) {
1204  XO("Unable to open genre file."),
1205  XO("Reset Genres") );
1206  mLocal.LoadGenres();
1207  return;
1208  }
1209 
1210  tf.Clear();
1211  int cnt = mLocal.GetNumUserGenres();
1212  for (int i = 0; i < cnt; i++) {
1213  tf.AddLine(mLocal.GetUserGenre(i));
1214  }
1215 
1216  if (!tf.Write()) {
1218  XO("Unable to save genre file."),
1219  XO("Reset Genres") );
1220  mLocal.LoadGenres();
1221  return;
1222  }
1223 
1224  mLocal.LoadGenres();
1225 
1226  PopulateGenres();
1227 }
1228 
1229 void TagsEditorDialog::OnClear(wxCommandEvent & WXUNUSED(event))
1230 {
1231  mLocal.Clear();
1232 
1234 }
1235 
1236 void TagsEditorDialog::OnLoad(wxCommandEvent & WXUNUSED(event))
1237 {
1238  wxString fn;
1239 
1240  // Ask the user for the real name
1241  fn = FileNames::SelectFile(FileNames::Operation::_None,
1242  XO("Load Metadata As:"),
1244  wxT("Tags.xml"),
1245  wxT("xml"),
1246  { FileNames::XMLFiles },
1247  wxFD_OPEN | wxRESIZE_BORDER,
1248  this);
1249 
1250  // User canceled...
1251  if (fn.empty()) {
1252  return;
1253  }
1254 
1255  // Load the metadata
1256  decltype(mLocal) temp;
1257  XMLFileReader reader;
1258  if (!reader.Parse(&temp, fn)) {
1259  // Inform user of load failure
1261  reader.GetErrorStr(),
1262  XO("Error Loading Metadata"),
1263  wxOK | wxCENTRE,
1264  this);
1265  return;
1266  }
1267 
1268  // Remember title and track in case they're read only
1269  wxString title = mLocal.GetTag(TAG_TITLE);
1270  wxString track = mLocal.GetTag(TAG_TRACK);
1271 
1272  // Replace existing tags with loaded ones
1273  mLocal = temp;
1274 
1275  // Restore title
1276  if (!mEditTitle) {
1277  mLocal.SetTag(TAG_TITLE, title);
1278  }
1279 
1280  // Restore track
1281  if (!mEditTrack) {
1282  mLocal.SetTag(TAG_TRACK, track);
1283  }
1284 
1285  // Go fill up the window
1287 
1288  return;
1289 }
1290 
1291 void TagsEditorDialog::OnSave(wxCommandEvent & WXUNUSED(event))
1292 {
1293  wxString fn;
1294 
1295  // Refresh tags
1297 
1298  // Ask the user for the real name
1299  fn = FileNames::SelectFile(FileNames::Operation::_None,
1300  XO("Save Metadata As:"),
1302  wxT("Tags.xml"),
1303  wxT("xml"),
1304  { FileNames::XMLFiles },
1305  wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
1306  this);
1307 
1308  // User canceled...
1309  if (fn.empty()) {
1310  return;
1311  }
1312 
1313  GuardedCall( [&] {
1314  // Create/Open the file
1315  XMLFileWriter writer{ fn, XO("Error Saving Tags File") };
1316 
1317  // Remember title and track in case they're read only
1318  wxString title = mLocal.GetTag(TAG_TITLE);
1319  wxString track = mLocal.GetTag(TAG_TRACK);
1320 
1321  // Clear title
1322  if (!mEditTitle) {
1323  mLocal.SetTag(TAG_TITLE, wxEmptyString);
1324  }
1325 
1326  // Clear track
1327  if (!mEditTrack) {
1328  mLocal.SetTag(TAG_TRACK, wxEmptyString);
1329  }
1330 
1331  auto cleanup = finally( [&] {
1332  // Restore title
1333  if (!mEditTitle) {
1334  mLocal.SetTag(TAG_TITLE, title);
1335  }
1336 
1337  // Restore track
1338  if (!mEditTrack) {
1339  mLocal.SetTag(TAG_TRACK, track);
1340  }
1341  } );
1342 
1343  // Write the metadata
1344  mLocal.WriteXML(writer);
1345 
1346  writer.Commit();
1347  } );
1348 }
1349 
1350 void TagsEditorDialog::OnSaveDefaults(wxCommandEvent & WXUNUSED(event))
1351 {
1352  // Refresh tags
1354 
1355  // Remember title and track in case they're read only
1356  wxString title = mLocal.GetTag(TAG_TITLE);
1357  wxString track = mLocal.GetTag(TAG_TRACK);
1358 
1359  // Clear title
1360  if (!mEditTitle) {
1361  mLocal.SetTag(TAG_TITLE, wxEmptyString);
1362  }
1363 
1364  // Clear track
1365  if (!mEditTrack) {
1366  mLocal.SetTag(TAG_TRACK, wxEmptyString);
1367  }
1368 
1369  // Remove any previous defaults
1370  gPrefs->DeleteGroup(wxT("/Tags"));
1371 
1372  // Write out each tag
1373  for (const auto &pair : mLocal.GetRange()) {
1374  const auto &n = pair.first;
1375  const auto &v = pair.second;
1376  gPrefs->Write(wxT("/Tags/") + n, v);
1377  }
1378  gPrefs->Flush();
1379 
1380  // Restore title
1381  if (!mEditTitle) {
1382  mLocal.SetTag(TAG_TITLE, title);
1383  }
1384 
1385  // Restore track
1386  if (!mEditTrack) {
1387  mLocal.SetTag(TAG_TRACK, track);
1388  }
1389 }
1390 
1391 void TagsEditorDialog::OnAdd(wxCommandEvent & WXUNUSED(event))
1392 {
1393  mGrid->AppendRows();
1394 }
1395 
1396 void TagsEditorDialog::OnRemove(wxCommandEvent & WXUNUSED(event))
1397 {
1398  size_t row = mGrid->GetGridCursorRow();
1399 
1400  if (!mEditTitle &&
1401  mGrid->GetCellValue(row, 0).CmpNoCase(LABEL_TITLE.Translation()) == 0) {
1402  return;
1403  }
1404  else if (!mEditTrack &&
1405  mGrid->GetCellValue(row, 0)
1406  .CmpNoCase(LABEL_TRACK.Translation()) == 0) {
1407  return;
1408  }
1409  else if (row < STATICCNT) {
1410  mGrid->SetCellValue(row, 1, wxEmptyString);
1411  }
1412  else if (row >= STATICCNT) {
1413  mGrid->DeleteRows(row, 1);
1414  }
1415 }
1416 
1417 void TagsEditorDialog::OnOk(wxCommandEvent & WXUNUSED(event))
1418 {
1419  if (mGrid->IsCellEditControlShown()) {
1420  mGrid->SaveEditControlValue();
1421  mGrid->HideCellEditControl();
1422 #if defined(__WXMAC__)
1423  // The cell editors do not capture the ENTER key, so it invokes
1424  // the default button ("Ok") when it should just close the
1425  // editor. So, cancel the "Ok" action.
1426  return;
1427 #endif
1428  }
1429 
1430  if (!Validate() || !TransferDataFromWindow()) {
1431  return;
1432  }
1433 
1434  *mTags = mLocal;
1435 
1436  wxRect r = GetRect();
1437  gPrefs->Write(wxT("/TagsEditorDialog/x"), r.x);
1438  gPrefs->Write(wxT("/TagsEditorDialog/y"), r.y);
1439  gPrefs->Write(wxT("/TagsEditorDialog/width"), r.width);
1440  gPrefs->Write(wxT("/TagsEditorDialog/height"), r.height);
1441  gPrefs->Flush();
1442 
1443  EndModal(wxID_OK);
1444 }
1445 
1446 void TagsEditorDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
1447 {
1448  DoCancel(false);
1449 }
1450 
1452 {
1453  if (mGrid->IsCellEditControlShown()) {
1454  auto editor = mGrid->GetCellEditor(mGrid->GetGridCursorRow(),
1455  mGrid->GetGridCursorCol());
1456  editor->Reset();
1457  // To avoid memory leak, don't forget DecRef()!
1458  editor->DecRef();
1459  mGrid->HideCellEditControl();
1460 #if defined(__WXMSW__)
1461  return;
1462 #endif
1463  }
1464 
1465  auto focus = wxWindow::FindFocus();
1466  if (escKey && focus == mGrid)
1467  return;
1468 
1469  EndModal(wxID_CANCEL);
1470 }
1471 
1472 void TagsEditorDialog::OnKeyDown(wxKeyEvent &event)
1473 {
1474  if (event.GetKeyCode() == WXK_ESCAPE)
1475  DoCancel(true);
1476  else
1477  event.Skip();
1478 }
1479 
1481 {
1482  int cnt = mGrid->GetNumberRows();
1483 
1484  for (int i = 0; i < cnt; i++) {
1485  wxString label = mGrid->GetCellValue(i, 0);
1486  if (label.CmpNoCase(LABEL_GENRE.Translation()) == 0) {
1487  // This use of GetDefaultEditorForType does not require DecRef.
1488  mGrid->SetCellEditor(i, 1, mGrid->GetDefaultEditorForType(wxT("Combo")));
1489  }
1490  else {
1491  mGrid->SetCellEditor(i, 1, NULL); //mGrid->GetDefaultEditor());
1492  }
1493  }
1494 }
1495 
1497 {
1498  int cnt = mLocal.GetNumUserGenres();
1499  int i;
1500  wxString parm;
1501  wxArrayString g;
1502 
1503  for (i = 0; i < cnt; i++) {
1504  g.push_back(mLocal.GetUserGenre(i));
1505  }
1506  std::sort( g.begin(), g.end() );
1507 
1508  for (i = 0; i < cnt; i++) {
1509  parm = parm + (i == 0 ? wxT("") : wxT(",")) + g[i];
1510  }
1511 
1512  // Here was a memory leak! wxWidgets docs for wxGrid::GetDefaultEditorForType() say:
1513  // "The caller must call DecRef() on the returned pointer."
1514  auto editor = mGrid->GetDefaultEditorForType(wxT("Combo"));
1515  editor->SetParameters(parm);
1516  editor->DecRef();
1517 }
1518 
1519 bool TagsEditorDialog::IsWindowRectValid(const wxRect *windowRect) const
1520 {
1521  wxDisplay display;
1522  wxPoint topLeft(windowRect->GetTopLeft().x, windowRect->GetTopLeft().y);
1523  wxPoint topRight(windowRect->GetTopRight().x, windowRect->GetTopRight().y);
1524  wxPoint bottomLeft(windowRect->GetBottomLeft().x, windowRect->GetBottomLeft().y);
1525  wxPoint bottomRight(windowRect->GetBottomRight().x, windowRect->GetBottomRight().y);
1526  display.GetFromPoint(topLeft);
1527  if (display.GetFromPoint(topLeft) == -1 &&
1528  display.GetFromPoint(topRight) == -1 &&
1529  display.GetFromPoint(bottomLeft) == -1 &&
1530  display.GetFromPoint(bottomRight) == -1) {
1531  return false;
1532  }
1533 
1534  return true;
1535 }
XMLWriter
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:23
TagsEditorDialog::OnOk
void OnOk(wxCommandEvent &event)
Definition: Tags.cpp:1417
EVT_BUTTON
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
TagsEditorDialog::OnLoad
void OnLoad(wxCommandEvent &event)
Definition: Tags.cpp:1236
FileConfig::SetPath
virtual void SetPath(const wxString &strPath) wxOVERRIDE
Definition: FileConfig.cpp:101
TranslatableString
Definition: Types.h:290
key
static const AudacityProject::AttachedObjects::RegisteredFactory key
Definition: Tags.cpp:232
ComboEditor::SetParameters
void SetParameters(const wxString &params) override
Definition: Tags.cpp:645
ComboEditor::PaintBackground
void PaintBackground(wxDC &, const wxRect &WXUNUSED(rectCell), const wxGridCellAttr &WXUNUSED(attr)) override
Definition: Tags.cpp:640
TagsEditorDialog::DoCancel
void DoCancel(bool escKey)
Definition: Tags.cpp:1451
eIsCreating
@ eIsCreating
Definition: ShuttleGui.h:36
TagsEditorDialog::OnSaveDefaults
void OnSaveDefaults(wxCommandEvent &event)
Definition: Tags.cpp:1350
TagsEditorDialog::OnEdit
void OnEdit(wxCommandEvent &event)
Definition: Tags.cpp:1131
Tags::LoadDefaultGenres
void LoadDefaultGenres()
Definition: Tags.cpp:392
ShuttleGuiBase::StartVerticalLay
void StartVerticalLay(int iProp=1)
Definition: ShuttleGui.cpp:1177
Tags::mEditTrackNumber
bool mEditTrackNumber
Definition: Tags.h:137
ShuttleGuiBase::AddCheckBox
wxCheckBox * AddCheckBox(const TranslatableString &Prompt, bool Selected)
Definition: ShuttleGui.cpp:302
Tags::HandleXMLTag
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override
Definition: Tags.cpp:545
TagsEditorDialog::OnCancel
void OnCancel(wxCommandEvent &event)
Definition: Tags.cpp:1446
fn
static const auto fn
Definition: WaveformView.cpp:1102
HelpSystem.h
ComboEditor::ComboEditor
ComboEditor(const wxArrayString &choices, bool allowOthers=false)
Definition: Tags.cpp:633
ShuttleGuiBase::AddTitle
void AddTitle(const TranslatableString &Prompt, int wrapWidth=0)
Centred text string.
Definition: ShuttleGui.cpp:274
registerFactory
static ProjectFileIORegistry::Entry registerFactory
Definition: Tags.cpp:227
Tags::IsEmpty
bool IsEmpty()
Definition: Tags.cpp:325
gPrefs
FileConfig * gPrefs
Definition: Prefs.cpp:67
AudacityMessageBox
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: AudacityMessageBox.h:20
ProjectFileIORegistry::Entry
Definition: ProjectFileIORegistry.h:27
Project.h
Tags::HasTag
bool HasTag(const wxString &name) const
Definition: Tags.cpp:451
Tags
ID3 Tags (for MP3)
Definition: Tags.h:74
TagsEditorDialog::OnKeyDown
void OnKeyDown(wxKeyEvent &event)
Definition: Tags.cpp:1472
TAG_TRACK
#define TAG_TRACK
Definition: Tags.h:63
ComboEditor::m_allowOthers
bool m_allowOthers
Definition: Tags.cpp:728
Tags::GetRange
Iterators GetRange() const
Definition: Tags.cpp:480
Grid
Supplies an accessible grid based on wxGrid.
Definition: Grid.h:185
Grid.h
TagsEditorDialog::OnClear
void OnClear(wxCommandEvent &event)
Definition: Tags.cpp:1229
FileNames::DataDir
AUDACITY_DLL_API FilePath DataDir()
Audacity user data directory.
IteratorRange
A convenience for use with range-for.
Definition: MemoryX.h:454
TagsEditorDialog::mComboEditor
ComboEditor * mComboEditor
Definition: Tags.h:200
Tags::LoadGenres
void LoadGenres()
Definition: Tags.cpp:400
Tags::Duplicate
std::shared_ptr< Tags > Duplicate() const
Definition: Tags.cpp:266
XO
#define XO(s)
Definition: Internat.h:32
FileNames::XMLFiles
AUDACITY_DLL_API const FileType XMLFiles
Definition: FileNames.h:77
Tags::GetUserGenre
wxString GetUserGenre(int value)
Definition: Tags.cpp:418
LABEL_TRACK
#define LABEL_TRACK
Definition: Tags.cpp:738
Tags::mXref
TagMap mXref
Definition: Tags.h:131
eCancelButton
@ eCancelButton
Definition: ShuttleGui.h:608
ShuttleGuiBase::EndMultiColumn
void EndMultiColumn()
Definition: ShuttleGui.cpp:1212
RemoveID
@ RemoveID
Definition: Tags.cpp:779
TagsEditorDialog::TransferDataToWindow
bool TransferDataToWindow() override
Definition: Tags.cpp:1032
TagsEditorDialog::mGrid
Grid * mGrid
Definition: Tags.h:199
LABEL_TITLE
#define LABEL_TITLE
Definition: Tags.cpp:736
TagsEditorDialog::mStringRenderer
wxGridCellStringRenderer * mStringRenderer
Definition: Tags.h:201
Tags::GetTag
wxString GetTag(const wxString &name) const
Definition: Tags.cpp:460
ResetID
@ ResetID
Definition: Tags.cpp:774
DontShowID
@ DontShowID
Definition: Tags.cpp:780
FileConfig::GetFirstEntry
virtual bool GetFirstEntry(wxString &str, long &lIndex) const wxOVERRIDE
Definition: FileConfig.cpp:121
ClientData::Site::RegisteredFactory
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
XMLValueChecker::IsGoodString
static bool IsGoodString(const wxString &str)
Definition: XMLTagHandler.cpp:39
Tags.h
FileConfig::GetNextEntry
virtual bool GetNextEntry(wxString &str, long &lIndex) const wxOVERRIDE
Definition: FileConfig.cpp:126
Tags::Get
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:236
ShuttleGui::Id
ShuttleGui & Id(int id)
Definition: ShuttleGui.cpp:2248
TagsEditorDialog::OnRemove
void OnRemove(wxCommandEvent &event)
Definition: Tags.cpp:1396
operator==
bool operator==(const Tags &lhs, const Tags &rhs)
Definition: Tags.cpp:360
XMLFileReader::Parse
bool Parse(XMLTagHandler *baseHandler, const FilePath &fname)
Definition: XMLFileReader.cpp:41
TagsEditorDialog::PopulateOrExchange
void PopulateOrExchange(ShuttleGui &S)
Definition: Tags.cpp:875
TagsEditorDialog::SetEditors
void SetEditors()
Definition: Tags.cpp:1480
SaveDefaultsID
@ SaveDefaultsID
Definition: Tags.cpp:777
eOkButton
@ eOkButton
Definition: ShuttleGui.h:607
TagsEditorDialog::mEditTitle
bool mEditTitle
Definition: Tags.h:194
TranslatableStrings
std::vector< TranslatableString > TranslatableStrings
Definition: Types.h:555
Tags::GetGenre
wxString GetGenre(int value)
Definition: Tags.cpp:427
Tags::AllowEditTitle
void AllowEditTitle(bool editTitle)
Definition: Tags.cpp:377
Tags::mEditTitle
bool mEditTitle
Definition: Tags.h:136
XXO
#define XXO(s)
Definition: Internat.h:45
ShuttleGuiBase::EndHorizontalLay
void EndHorizontalLay()
Definition: ShuttleGui.cpp:1170
XMLFileReader::GetErrorStr
const TranslatableString & GetErrorStr() const
Definition: XMLFileReader.cpp:177
DefaultGenres
static const wxChar * DefaultGenres[]
Definition: Tags.cpp:72
Tags::SetTag
void SetTag(const wxString &name, const wxString &value, const bool bSpecialTag=false)
Definition: Tags.cpp:485
anonymous_namespace{Tags.cpp}::EqualMaps
bool EqualMaps(const TagMap &map1, const TagMap &map2)
Definition: Tags.cpp:343
label
TranslatableString label
Definition: Tags.cpp:755
ShuttleGuiBase::StartHorizontalLay
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
Definition: ShuttleGui.cpp:1160
Tags::mGenres
wxArrayString mGenres
Definition: Tags.h:134
Tags::~Tags
virtual ~Tags()
Definition: Tags.cpp:262
Tags::mMap
TagMap mMap
Definition: Tags.h:132
ShuttleGuiBase::StartMultiColumn
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
Definition: ShuttleGui.cpp:1203
ShuttleGuiBase::EndVerticalLay
void EndVerticalLay()
Definition: ShuttleGui.cpp:1196
TagsEditorDialog
Derived from ExpandingToolBar, this dialog allows editing of Tags.
Definition: Tags.h:144
LABEL_ALBUM
#define LABEL_ALBUM
Definition: Tags.cpp:737
eHelpButton
@ eHelpButton
Definition: ShuttleGui.h:611
ShuttleGuiBase::AddUnits
void AddUnits(const TranslatableString &Prompt, int wrapWidth=0)
Left aligned text string.
Definition: ShuttleGui.cpp:256
EditID
@ EditID
Definition: Tags.cpp:773
Tags::Clear
void Clear()
Definition: Tags.cpp:336
TagsEditorDialog::OnDontShow
void OnDontShow(wxCommandEvent &Evt)
Definition: Tags.cpp:966
TAG_GENRE
#define TAG_GENRE
Definition: Tags.h:65
labelmap
static const struct @0 labelmap[]
ShuttleGuiBase::GetParent
wxWindow * GetParent()
Definition: ShuttleGui.h:503
Tags::GetNumUserGenres
int GetNumUserGenres()
Definition: Tags.cpp:387
TagsEditorDialog::OnReset
void OnReset(wxCommandEvent &event)
Definition: Tags.cpp:1184
TAG_YEAR
#define TAG_YEAR
Definition: Tags.h:64
Tags::operator=
Tags & operator=(const Tags &src)
Definition: Tags.cpp:278
TagsEditorDialog::TransferDataFromWindow
bool TransferDataFromWindow() override
Definition: Tags.cpp:978
anonymous_namespace{TracksPrefs.cpp}::key0
const auto key0
Definition: TracksPrefs.cpp:78
wxDialogWrapper::SetName
void SetName(const TranslatableString &title)
Definition: wxPanelWrapper.cpp:76
XMLFileReader.h
TagsEditorDialog::PopulateGenres
void PopulateGenres()
Definition: Tags.cpp:1496
ShuttleGui.h
ProjectFileIORegistry.h
ComboEditor::StartingKey
virtual void StartingKey(wxKeyEvent &event) override
Definition: Tags.cpp:670
Tags::Set
static Tags & Set(AudacityProject &project, const std::shared_ptr< Tags > &tags)
Definition: Tags.cpp:246
ClearID
@ ClearID
Definition: Tags.cpp:772
ShuttleGui::Prop
ShuttleGui & Prop(int iProp)
Definition: ShuttleGui.h:732
XMLTagHandler
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:80
ShuttleGuiBase::AddButton
wxButton * AddButton(const TranslatableString &Text, int PositionFlags=wxALIGN_CENTRE, bool setDefault=false)
Definition: ShuttleGui.cpp:353
TagsEditorDialog::OnSave
void OnSave(wxCommandEvent &event)
Definition: Tags.cpp:1291
ComboEditor::SetSize
void SetSize(const wxRect &rectOrig) override
Definition: Tags.cpp:656
ShuttleGuiBase::StartStatic
wxStaticBox * StartStatic(const TranslatableString &Str, int iProp=0)
Definition: ShuttleGui.cpp:886
SaveID
@ SaveID
Definition: Tags.cpp:776
FileConfig::DeleteGroup
virtual bool DeleteGroup(const wxString &key) wxOVERRIDE
Definition: FileConfig.cpp:227
wxDialogWrapper
Definition: wxPanelWrapper.h:81
LABEL_COMMENTS
#define LABEL_COMMENTS
Definition: Tags.cpp:741
TagMap
std::unordered_map< wxString, wxString > TagMap
Definition: Tags.h:58
ShuttleGuiBase::AddWindow
wxWindow * AddWindow(wxWindow *pWindow)
Definition: ShuttleGui.cpp:292
FileConfig::Flush
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:151
HelpSystem::ShowHelp
static void ShowHelp(wxWindow *parent, const wxString &localFileName, const wxString &remoteURL, bool bModal=false, bool alwaysDefaultBrowser=false)
Definition: HelpSystem.cpp:238
TagsEditorDialog::mLocal
Tags mLocal
Definition: Tags.h:197
Tags::HandleXMLChild
XMLTagHandler * HandleXMLChild(const wxChar *tag) override
Definition: Tags.cpp:586
XMLFileReader
Reads a file and passes the results through an XMLTagHandler.
Definition: XMLFileReader.h:20
FileNames::SelectFile
AUDACITY_DLL_API FilePath SelectFile(Operation op, const TranslatableString &message, const FilePath &default_path, const FilePath &default_filename, const FileExtension &default_extension, const FileTypes &fileTypes, int flags, wxWindow *parent)
ShuttleGui::Position
ShuttleGui & Position(int flags)
Definition: ShuttleGui.h:719
TagsEditorDialog::IsWindowRectValid
bool IsWindowRectValid(const wxRect *windowRect) const
Definition: Tags.cpp:1519
names
static TranslatableStrings names
Definition: Tags.cpp:743
XMLValueChecker::IsGoodLongString
static bool IsGoodLongString(const wxString &str)
Definition: XMLTagHandler.cpp:52
_
#define _(s)
Definition: Internat.h:76
Tags::WriteXML
void WriteXML(XMLWriter &xmlFile) const
Definition: Tags.cpp:599
ComboEditor
Definition: Tags.cpp:631
AudacityProject
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:112
XMLFileWriter
Wrapper to output XML data to files.
Definition: XMLWriter.h:81
Tags::LoadDefaults
void LoadDefaults()
Definition: Tags.cpp:294
TagsEditorDialog::mEditTrack
bool mEditTrack
Definition: Tags.h:195
LABEL_YEAR
#define LABEL_YEAR
Definition: Tags.cpp:739
FileNames.h
GuardedCall
R GuardedCall(const F1 &body, const F2 &handler=F2::Default(), const F3 &delayedHandler={})
Execute some code on any thread; catch any AudacityException; enqueue error report on the main thread...
Definition: AudacityException.h:189
AudacityMessageBox.h
STATICCNT
#define STATICCNT
Definition: Tags.cpp:769
TagsEditorDialog::OnHelp
void OnHelp(wxCommandEvent &Evt)
Definition: Tags.cpp:973
TAG_COMMENTS
#define TAG_COMMENTS
Definition: Tags.h:66
ComboEditor::m_choices
wxArrayString m_choices
Definition: Tags.cpp:727
TagsEditorDialog::mTags
Tags * mTags
Definition: Tags.h:193
TAG_ARTIST
#define TAG_ARTIST
Definition: Tags.h:61
ShuttleGuiBase::AddTextWindow
wxTextCtrl * AddTextWindow(const wxString &Value)
Multiline text box that grows.
Definition: ShuttleGui.cpp:705
LABEL_GENRE
#define LABEL_GENRE
Definition: Tags.cpp:740
LoadID
@ LoadID
Definition: Tags.cpp:775
ShuttleGui::AddStandardButtons
void AddStandardButtons(long buttons=eOkButton|eCancelButton, wxWindow *extra=NULL)
Definition: ShuttleGui.cpp:2406
Prefs.h
params
EffectDistortion::Params params
Definition: Distortion.cpp:99
TranslatableString::Translation
wxString Translation() const
Definition: Types.h:337
name
wxString name
Definition: Tags.cpp:756
ShuttleGuiBase::EndStatic
void EndStatic()
Definition: ShuttleGui.cpp:915
safenew
#define safenew
Definition: MemoryX.h:8
TagsEditorDialog::~TagsEditorDialog
virtual ~TagsEditorDialog()
Definition: Tags.cpp:862
END_EVENT_TABLE
END_EVENT_TABLE()
TagsEditorDialog::OnAdd
void OnAdd(wxCommandEvent &event)
Definition: Tags.cpp:1391
Tags::AllowEditTrackNumber
void AllowEditTrackNumber(bool editTrackNumber)
Definition: Tags.cpp:382
Tags::Merge
void Merge(const Tags &other)
Definition: Tags.cpp:271
TAG_TITLE
#define TAG_TITLE
Definition: Tags.h:60
LABEL_ARTIST
#define LABEL_ARTIST
Definition: Tags.cpp:735
TagsEditorDialog::OnChange
void OnChange(wxGridEvent &event)
Definition: Tags.cpp:1094
ShuttleGui
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:638
FileConfig::GetPath
virtual const wxString & GetPath() const wxOVERRIDE
Definition: FileConfig.cpp:106
ComboEditor::Clone
wxGridCellEditor * Clone() const override
Definition: Tags.cpp:721
Tags::Tags
Tags()
Definition: Tags.cpp:253
Tags::ShowEditDialog
bool ShowEditDialog(wxWindow *parent, const TranslatableString &title, bool force=false)
Definition: Tags.cpp:616
TAG_ALBUM
#define TAG_ALBUM
Definition: Tags.h:62
AddID
@ AddID
Definition: Tags.cpp:778