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