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