1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   LabelDialog.cpp
6 
7   Dominic Mazzoni
8 
9 *******************************************************************//**
10 
11 \class LabelDialog
12 \brief Dialog for editing labels.
13 
14 *//*******************************************************************/
15 
16 
17 #include "LabelDialog.h"
18 
19 #include <wx/button.h>
20 #include <wx/defs.h>
21 #include <wx/choice.h>
22 #include <wx/dc.h>
23 #include <wx/dialog.h>
24 #include <wx/grid.h>
25 #include <wx/intl.h>
26 #include <wx/scrolbar.h>
27 #include <wx/settings.h>
28 #include <wx/sizer.h>
29 #include <wx/stattext.h>
30 #include <wx/textdlg.h>
31 
32 #include "ShuttleGui.h"
33 #include "LabelTrack.h"
34 #include "Prefs.h"
35 #include "Project.h"
36 #include "ProjectWindow.h"
37 #include "SelectFile.h"
38 #include "ViewInfo.h"
39 #include "tracks/labeltrack/ui/LabelTrackView.h"
40 #include "widgets/AudacityMessageBox.h"
41 #include "widgets/AudacityTextEntryDialog.h"
42 #include "widgets/Grid.h"
43 #include "widgets/HelpSystem.h"
44 
45 #include "FileNames.h"
46 #include <limits>
47 
48 enum Column
49 {
50    Col_Track,
51    Col_Label,
52    Col_Stime,
53    Col_Etime,
54    Col_Lfreq,
55    Col_Hfreq,
56    Col_Max
57 };
58 
59 
60 
61 class RowData
62 {
63  public:
RowData(int index_,const wxString & title_,const SelectedRegion & selectedRegion_)64    RowData(int index_, const wxString &title_, const SelectedRegion &selectedRegion_)
65       : index(index_), title(title_), selectedRegion(selectedRegion_)
66    {}
67 
68    int index;
69    wxString title;
70    SelectedRegion selectedRegion;
71 };
72 
73 enum {
74    ID_INSERTA = 11000,
75    ID_INSERTB,
76    ID_REMOVE,
77    ID_IMPORT,
78    ID_EXPORT
79 };
80 
BEGIN_EVENT_TABLE(LabelDialog,wxDialogWrapper)81 BEGIN_EVENT_TABLE(LabelDialog, wxDialogWrapper)
82    EVT_GRID_SELECT_CELL(LabelDialog::OnSelectCell)
83    EVT_GRID_CELL_CHANGED(LabelDialog::OnCellChange)
84    EVT_BUTTON(ID_INSERTA, LabelDialog::OnInsert)
85    EVT_BUTTON(ID_INSERTB, LabelDialog::OnInsert)
86    EVT_BUTTON(ID_REMOVE,  LabelDialog::OnRemove)
87    EVT_BUTTON(ID_IMPORT,  LabelDialog::OnImport)
88    EVT_BUTTON(ID_EXPORT,  LabelDialog::OnExport)
89    EVT_BUTTON(wxID_OK,      LabelDialog::OnOK)
90    EVT_BUTTON(wxID_CANCEL,  LabelDialog::OnCancel)
91    EVT_COMMAND(wxID_ANY, EVT_TIMETEXTCTRL_UPDATED, LabelDialog::OnUpdate)
92    EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED,
93                LabelDialog::OnFreqUpdate)
94    EVT_BUTTON(wxID_HELP, LabelDialog::OnHelp)
95 END_EVENT_TABLE()
96 
97 LabelDialog::LabelDialog(wxWindow *parent,
98                          AudacityProject &project,
99                          TrackList *tracks,
100                          LabelTrack *selectedTrack,
101                          int index,
102                          ViewInfo &viewinfo,
103                          double rate,
104                          const NumericFormatSymbol & format,
105                          const NumericFormatSymbol &freqFormat)
106 : wxDialogWrapper(parent,
107            wxID_ANY,
108            XO("Edit Labels"),
109            wxDefaultPosition,
110            wxSize(800, 600),
111            wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
112   , mProject{ project }
113   , mTracks(tracks)
114   , mSelectedTrack(selectedTrack)
115   , mIndex(index)
116   , mViewInfo(&viewinfo),
117   mRate(rate),
118   mFormat(format)
119   , mFreqFormat(freqFormat)
120 {
121    SetName();
122    Populate();
123 }
124 
~LabelDialog()125 LabelDialog::~LabelDialog()
126 {
127 }
128 
PopulateLabels()129 void LabelDialog::PopulateLabels()
130 {
131    // Build the initial (empty) grid
132    mGrid->CreateGrid(0, Col_Max, wxGrid::wxGridSelectRows);
133    mGrid->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTER);
134    mGrid->SetRowLabelSize(0);
135 
136    size_t ii = 0;
137    for ( const auto &label : {
138       /* i18n-hint: (noun).  A track contains waves, audio etc.*/
139       XO("Track"),
140       /* i18n-hint: (noun)*/
141       XO("Label"),
142       /* i18n-hint: (noun) of a label*/
143       XO("Start Time"),
144       /* i18n-hint: (noun) of a label*/
145       XO("End Time"),
146       /* i18n-hint: (noun) of a label*/
147       XO("Low Frequency"),
148       /* i18n-hint: (noun) of a label*/
149       XO("High Frequency"),
150    })
151       mGrid->SetColLabelValue( ii++, label.Translation() );
152 
153    // Create and remember editors.  No need to DELETE these as the wxGrid will
154    // do it for us.  (The DecRef() that is needed after GetDefaultEditorForType
155    // becomes the duty of the wxGridCellAttr objects after we set them in the grid.)
156    mChoiceEditor = (ChoiceEditor *) mGrid->GetDefaultEditorForType(GRID_VALUE_CHOICE);
157    mTimeEditor = static_cast<NumericEditor*>
158       (mGrid->GetDefaultEditorForType(GRID_VALUE_TIME));
159    mFrequencyEditor = static_cast<NumericEditor *>
160       (mGrid->GetDefaultEditorForType(GRID_VALUE_FREQUENCY));
161 
162    // Initialize and set the track name column attributes
163    wxGridCellAttr *attr;
164    mGrid->SetColAttr(Col_Track, (attr = safenew wxGridCellAttr));
165    attr->SetEditor(mChoiceEditor);
166    mTrackNames.push_back(_("New..."));
167 
168    // Initialize and set the time column attributes
169    mGrid->SetColAttr(Col_Stime, (attr = safenew wxGridCellAttr));
170    // Don't need DecRef() after this GetDefaultRendererForType.
171    attr->SetRenderer(mGrid->GetDefaultRendererForType(GRID_VALUE_TIME));
172    attr->SetEditor(mTimeEditor);
173    attr->SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
174 
175    mGrid->SetColAttr(Col_Etime, attr->Clone());
176 
177    // Initialize and set the frequency column attributes
178    mGrid->SetColAttr(Col_Lfreq, (attr = safenew wxGridCellAttr));
179    // Don't need DecRef() after this GetDefaultRendererForType.
180    attr->SetRenderer(mGrid->GetDefaultRendererForType(GRID_VALUE_FREQUENCY));
181    attr->SetEditor(mFrequencyEditor);
182    attr->SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
183 
184    mGrid->SetColAttr(Col_Hfreq, attr->Clone());
185 
186    // Seems there's a bug in wxGrid.  Adding only 1 row does not
187    // allow SetCellSize() to work properly and you will not get
188    // the expected 1 row by 4 column cell.
189    //
190    // So, we set the minimum row height to 0 and basically hide
191    // the extra row by setting its height to 0.  And not allowing the
192    // rows to be manually resized prevents the user from ever seeing
193    // the extra row.
194    mGrid->SetRowMinimalAcceptableHeight(0);
195    mGrid->EnableDragRowSize(false);
196 
197    // Locate all labels in current track list
198    FindAllLabels();
199 
200    // Populate the grid
201    TransferDataToWindow();
202 
203    // Resize the label name column and ensure it doesn't go below an
204    // arbitrary width.
205    //
206    // This should not be in TransferDataToWindow() since a user might
207    // resize the column and we'd resize it back to the minimum.
208    mGrid->AutoSizeColumn(Col_Label, false );
209    mGrid->SetColSize(Col_Label, wxMax(150, mGrid->GetColSize(Col_Label)));
210    mGrid->SetColMinimalWidth(Col_Label, mGrid->GetColSize(Col_Label));
211 
212 }
213 
214 
215 /// Creates the dialog and its contents.
Populate()216 void LabelDialog::Populate()
217 {
218 
219    //------------------------- Main section --------------------
220    ShuttleGui S(this, eIsCreating);
221    PopulateOrExchange(S);
222    // ----------------------- End of main section --------------
223 
224    // Go populate the macros list.
225    PopulateLabels();
226 
227    // Layout the works
228    Layout();
229    //Fit();
230 
231    // Resize width based on width of columns and the vertical scrollbar
232    wxRect r = mGrid->GetGridColLabelWindow()->GetRect();
233    wxScrollBar sb(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL);
234    r.width += sb.GetSize().GetWidth() + 6;
235 
236    // Add the size of the right column of buttons too...
237    wxWindow * w = FindWindowById( ID_IMPORT, this );
238    wxASSERT( w );
239    if( w )
240       r.width += w->GetSize().GetWidth();
241 
242    SetClientSize(r.width, 300);
243 
244    // Make sure it doesn't go below this size
245    r = GetRect();
246    SetSizeHints(r.GetWidth(), r.GetHeight());
247 
248    // Bug 1465
249    // There might be a saved size, in which case use that.
250    ReadSize();
251 
252    // Center on display
253    Center();
254 }
255 
PopulateOrExchange(ShuttleGui & S)256 void LabelDialog::PopulateOrExchange( ShuttleGui & S )
257 {
258    S.AddFixedText(XO("Press F2 or double click to edit cell contents."));
259    S.StartHorizontalLay(wxEXPAND,1);
260    {
261       S.StartVerticalLay(wxEXPAND,1);
262       {
263          mGrid = safenew Grid(S.GetParent(), wxID_ANY);
264          S.Prop(1).AddWindow( mGrid );
265       }
266       S.EndVerticalLay();
267       S.StartVerticalLay(0);
268       {
269          //S.Id(ID_INSERTA).AddButton(XO("&Insert"), wxALIGN_LEFT);
270          S.Id(ID_INSERTB).AddButton(XXO("&Insert"), wxALIGN_LEFT);
271          //S.Id(EditButtonID).AddButton(XO("&Edit"), wxALIGN_LEFT);
272          S.Id(ID_REMOVE).AddButton(XXO("De&lete"), wxALIGN_LEFT);
273          S.Id(ID_IMPORT).AddButton(XXO("I&mport..."), wxALIGN_LEFT);
274          S.Id(ID_EXPORT).AddButton(XXO("&Export..."), wxALIGN_LEFT);
275       }
276       S.EndVerticalLay();
277    }
278    S.EndHorizontalLay();
279 
280    S.StartHorizontalLay(wxALIGN_RIGHT, false);
281    {
282       S.AddStandardButtons( eOkButton | eCancelButton | eHelpButton);
283    }
284    S.EndHorizontalLay();
285 }
286 
OnHelp(wxCommandEvent & WXUNUSED (event))287 void LabelDialog::OnHelp(wxCommandEvent & WXUNUSED(event))
288 {
289    const auto &page = GetHelpPageName();
290    HelpSystem::ShowHelp(this, page, true);
291 }
292 
293 
TransferDataToWindow()294 bool LabelDialog::TransferDataToWindow()
295 {
296    int cnt = mData.size();
297    int i;
298 
299    // Set the editor parameters.  Do this each time since they may change
300    // due to NEW tracks and change in NumericTextCtrl format.  Rate won't
301    // change but might as well leave it here.
302    mChoiceEditor->SetChoices(mTrackNames);
303    mTimeEditor->SetFormat(mFormat);
304    mTimeEditor->SetRate(mRate);
305    mFrequencyEditor->SetFormat(mFreqFormat);
306    mFrequencyEditor->SetRate(mRate);
307 
308    // Disable redrawing until we're done
309    mGrid->BeginBatch();
310 
311    // Delete all rows
312    if (mGrid->GetNumberRows()) {
313       mGrid->DeleteRows(0, mGrid->GetNumberRows());
314    }
315 
316    // Add the exact number that we'll need
317    mGrid->InsertRows(0, cnt);
318 
319    // Populate the rows
320    for (i = 0; i < cnt; i++) {
321       RowData &rd = mData[i];
322 
323       // Set the cell contents
324       mGrid->SetCellValue(i, Col_Track, TrackName(rd.index));
325       mGrid->SetCellValue(i, Col_Label, rd.title);
326       mGrid->SetCellValue(i, Col_Stime,
327          wxString::Format(wxT("%g"), rd.selectedRegion.t0()));
328       mGrid->SetCellValue(i, Col_Etime,
329          wxString::Format(wxT("%g"), rd.selectedRegion.t1()));
330       mGrid->SetCellValue(i, Col_Lfreq,
331          wxString::Format(wxT("%g"), rd.selectedRegion.f0()));
332       mGrid->SetCellValue(i, Col_Hfreq,
333          wxString::Format(wxT("%g"), rd.selectedRegion.f1()));
334    }
335 
336    // Autosize all the rows
337    mGrid->AutoSizeRows(true);
338 
339    // Resize the track name column.  Use a wxChoice to determine the maximum
340    // width needed.
341    wxChoice tc(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, mTrackNames);
342    mGrid->SetColSize(Col_Track, tc.GetSize().x);
343    mGrid->SetColMinimalWidth(Col_Track, tc.GetSize().x);
344 
345    // Autosize the time columns and set their minimal widths
346    mGrid->AutoSizeColumn(Col_Stime);
347    mGrid->AutoSizeColumn(Col_Etime);
348    mGrid->AutoSizeColumn(Col_Lfreq);
349    mGrid->AutoSizeColumn(Col_Hfreq);
350 
351    // We're done, so allow the grid to redraw
352    mGrid->EndBatch();
353 
354    return true;
355 }
356 
Show(bool show)357 bool LabelDialog::Show(bool show)
358 {
359    bool ret = wxDialogWrapper::Show(show);
360 
361 #if defined(__WXMAC__) || defined(__WXGTK__)
362    if (show) {
363       mGrid->SetFocus();   // Required for Linux and Mac.
364    }
365 #endif
366 
367    // Set initial row
368    // (This will not work until the grid is actually displayed)
369    if (show && mInitialRow != -1) {
370       mGrid->GoToCell(mInitialRow, Col_Label);
371    }
372 
373    return ret;
374 }
375 
TransferDataFromWindow()376 bool LabelDialog::TransferDataFromWindow()
377 {
378    int cnt = mData.size();
379    int i;
380    int tndx = 0;
381 
382    // Clear label tracks of labels
383    for (auto lt : mTracks->Any<LabelTrack>()) {
384       ++tndx;
385       if (!mSelectedTrack) {
386          for (i = lt->GetNumLabels() - 1; i >= 0 ; i--) {
387             lt->DeleteLabel(i);
388          }
389       }
390       else if (mSelectedTrack == lt && mIndex > -1) {
391          lt->DeleteLabel(mIndex);
392       }
393       else
394          // Do nothing to the nonselected tracks
395          ;
396    }
397 
398    // Create any added tracks
399    while (tndx < (int)mTrackNames.size() - 1) {
400 
401       // Extract the name
402       wxString name = mTrackNames[tndx + 1].AfterFirst(wxT('-')).Mid(1);
403 
404       // Create the NEW track and add to track list
405       auto newTrack = std::make_shared<LabelTrack>();
406       newTrack->SetName(name);
407       mTracks->Add( newTrack );
408       tndx++;
409    }
410 
411    // Repopulate with updated labels
412    for (i = 0; i < cnt; i++) {
413       RowData &rd = mData[i];
414 
415       // Look for track with matching index
416       tndx = 1;
417       LabelTrack *lt{};
418       for (auto t : mTracks->Any<LabelTrack>()) {
419          lt = t;
420          if (rd.index == tndx++) {
421             break;
422          }
423       }
424       wxASSERT(lt);
425       if (!lt)
426          return false;
427 
428       // Add the label to it
429       lt->AddLabel(rd.selectedRegion, rd.title);
430       LabelTrackView::Get( *lt ).ResetTextSelection();
431    }
432 
433    return true;
434 }
435 
Validate()436 bool LabelDialog::Validate()
437 {
438    if (mGrid->IsCellEditControlShown()) {
439       mGrid->HideCellEditControl();
440       mGrid->SaveEditControlValue();
441    }
442 
443    return true;
444 }
445 
TrackName(int & index,const wxString & dflt)446 wxString LabelDialog::TrackName(int & index, const wxString &dflt)
447 {
448    // Generate a NEW track name if the passed index is out of range
449    if (index < 1 || index >= (int)mTrackNames.size()) {
450       index = mTrackNames.size();
451       mTrackNames.push_back(wxString::Format(wxT("%d - %s"), index, dflt));
452    }
453 
454    // Return the track name
455    return mTrackNames[index];
456 }
457 
FindAllLabels()458 void LabelDialog::FindAllLabels()
459 {
460    // Add labels from all label tracks
461    for (auto lt : mTracks->Any<const LabelTrack>()) {
462       AddLabels(lt);
463    }
464 
465    FindInitialRow();
466 
467    if (mData.size() == 0) {
468       wxCommandEvent e;
469       OnInsert(e);
470    }
471 }
472 
AddLabels(const LabelTrack * t)473 void LabelDialog::AddLabels(const LabelTrack *t)
474 {
475    wxString lab;
476    int tndx = 0;
477    int i;
478 
479    // Add a NEW track name
480    TrackName(tndx, t->GetName());
481 
482    // If editor was invoked for one label, add that one only, else add all.
483    if (!mSelectedTrack || mSelectedTrack == t) {
484       for (i = 0; i < t->GetNumLabels(); i++) {
485          const LabelStruct *ls = t->GetLabel(i);
486 
487          if (mIndex < 0 || mIndex == i)
488             mData.push_back(RowData(tndx, ls->title, ls->selectedRegion));
489       }
490    }
491 }
492 
FindInitialRow()493 void LabelDialog::FindInitialRow()
494 {
495    int cnt = mData.size();
496    mInitialRow = -1;
497 
498    if (cnt == 0)
499       return;
500 
501    // find closest previous label
502 
503    double distMin = std::numeric_limits<double>::max();
504    double dist;
505    double t0 = mViewInfo->selectedRegion.t0();
506    int i;
507    for (i = 0; i < cnt; i++)
508    {
509       dist = t0 - mData[i].selectedRegion.t0();
510       if (dist >= 0.0 && dist < distMin)
511       {
512          mInitialRow = i;
513          distMin = dist;
514       }
515    }
516 
517    // if no previous label was found, find first label
518 
519    if (mInitialRow == -1)
520    {
521       double t0Min = std::numeric_limits<double>::max();
522       for (i = 0; i < cnt; i++)
523       {
524          if (mData[i].selectedRegion.t0() < t0Min)
525          {
526             mInitialRow  = i;
527             t0Min = mData[i].selectedRegion.t0();
528          }
529       }
530    }
531 }
532 
OnUpdate(wxCommandEvent & event)533 void LabelDialog::OnUpdate(wxCommandEvent &event)
534 {
535    // Remember the NEW format and repopulate grid
536    mFormat = NumericConverter::LookupFormat(
537       NumericConverter::TIME, event.GetString() );
538    TransferDataToWindow();
539 
540    event.Skip(false);
541 }
542 
OnFreqUpdate(wxCommandEvent & event)543 void LabelDialog::OnFreqUpdate(wxCommandEvent &event)
544 {
545    // Remember the NEW format and repopulate grid
546    mFreqFormat = NumericConverter::LookupFormat(
547       NumericConverter::FREQUENCY, event.GetString() );
548    TransferDataToWindow();
549 
550    event.Skip(false);
551 }
552 
OnInsert(wxCommandEvent & event)553 void LabelDialog::OnInsert(wxCommandEvent &event)
554 {
555    int cnt = mData.size();
556    int row = 0;
557    int index = 0;
558 
559    // Make sure the edit control isn't active before inserting any rows
560    if (mGrid->IsCellEditControlShown()) {
561       mGrid->HideCellEditControl();
562    }
563 
564    // Attempt to guess which track the label should reside on
565    if (cnt > 0) {
566       row = mGrid->GetGridCursorRow();
567       if (row > 0 && row >= cnt) {
568          index = make_iterator_range( mTrackNames )
569             .index( mGrid->GetCellValue(row - 1, Col_Track) );
570       }
571       else {
572          index = make_iterator_range( mTrackNames )
573             .index( mGrid->GetCellValue(row, Col_Track) );
574       }
575    }
576 
577    // Insert NEW label before or after the current row
578    if (event.GetId() == ID_INSERTA && row < cnt) {
579       row++;
580    }
581    mData.insert(mData.begin() + row, RowData(index, wxT(""), SelectedRegion()));
582 
583    // Repopulate the grid
584    TransferDataToWindow();
585 
586    // Reposition cursor to NEW row/col and put user into edit mode to
587    // set the label name
588    mGrid->SetGridCursor(row, Col_Label);
589    mGrid->EnableCellEditControl(true);
590    mGrid->ShowCellEditControl();
591 }
592 
OnRemove(wxCommandEvent & WXUNUSED (event))593 void LabelDialog::OnRemove(wxCommandEvent & WXUNUSED(event))
594 {
595    int row = mGrid->GetGridCursorRow();
596    int col = mGrid->GetGridCursorCol();
597    int cnt = mData.size();
598 
599    // Don't try to remove if no labels exist
600    if (cnt == 0) {
601       return;
602    }
603 
604    // Make sure the edit control isn't active before removing rows
605    if (mGrid->IsCellEditControlShown()) {
606       mGrid->HideCellEditControl();
607    }
608 
609    // Remove the row
610    //RowData &rd = mData[row];
611    mData.erase(mData.begin() + row);
612 
613    // Repopulate the grid
614    TransferDataToWindow();
615 
616    // Reposition the cursor
617    if (row > 0 && row >= --cnt) {
618       row--;
619    }
620    mGrid->SetGridCursor(row, col);
621 
622    // Make sure focus isn't lost
623    if (mData.size() == 0 && wxWindow::FindFocus() == mGrid->GetGridWindow()) {
624       wxWindow *ok = wxWindow::FindWindowById( wxID_OK, this);
625       if (ok) {
626          ok->SetFocus();
627       }
628    }
629 }
630 
OnImport(wxCommandEvent & WXUNUSED (event))631 void LabelDialog::OnImport(wxCommandEvent & WXUNUSED(event))
632 {
633    // Ask user for a filename
634    wxString fileName =
635        SelectFile(FileNames::Operation::Open,
636          XO("Select a text file containing labels"),
637          wxEmptyString,     // Path
638          wxT(""),       // Name
639          wxT("txt"),   // Extension
640          { FileNames::TextFiles, FileNames::AllFiles },
641          wxRESIZE_BORDER, // Flags
642          this);    // Parent
643 
644    // They gave us one...
645    if (!fileName.empty()) {
646       wxTextFile f;
647 
648       // Get at the data
649       f.Open(fileName);
650       if (!f.IsOpened()) {
651          AudacityMessageBox(
652             XO("Could not open file: %s").Format( fileName ) );
653       }
654       else {
655          // Create a temporary label track and load the labels
656          // into it
657          auto lt = std::make_shared<LabelTrack>();
658          lt->Import(f);
659 
660          // Add the labels to our collection
661          AddLabels(lt.get());
662 
663          // Done with the temporary track
664      }
665 
666       // Repopulate the grid
667       TransferDataToWindow();
668    }
669 }
670 
OnExport(wxCommandEvent & WXUNUSED (event))671 void LabelDialog::OnExport(wxCommandEvent & WXUNUSED(event))
672 {
673    int cnt = mData.size();
674 
675    // Silly user (could just disable the button, but that's a hassle ;-))
676    if (cnt == 0) {
677       AudacityMessageBox( XO("No labels to export.") );
678       return;
679    }
680 
681    // Extract the actual name.
682    wxString fName = mTrackNames[mTrackNames.size() - 1].AfterFirst(wxT('-')).Mid(1);
683 
684    fName = SelectFile(FileNames::Operation::Export,
685       XO("Export Labels As:"),
686       wxEmptyString,
687       fName,
688       wxT("txt"),
689       { FileNames::TextFiles },
690       wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
691       this);
692 
693    if (fName.empty())
694       return;
695 
696    // Move existing files out of the way.  Otherwise wxTextFile will
697    // append to (rather than replace) the current file.
698 
699    if (wxFileExists(fName)) {
700 #ifdef __WXGTK__
701       wxString safetyFileName = fName + wxT("~");
702 #else
703       wxString safetyFileName = fName + wxT(".bak");
704 #endif
705 
706       if (wxFileExists(safetyFileName))
707          wxRemoveFile(safetyFileName);
708 
709       wxRename(fName, safetyFileName);
710    }
711 
712    wxTextFile f(fName);
713 #ifdef __WXMAC__
714    wxFile{}.Create(fName);
715 #else
716    f.Create();
717 #endif
718    f.Open();
719    if (!f.IsOpened()) {
720       AudacityMessageBox(
721          XO("Couldn't write to file: %s").Format( fName ) );
722       return;
723    }
724 
725    // Transfer our collection to a temporary label track
726    auto lt = std::make_shared<LabelTrack>();
727    int i;
728 
729    for (i = 0; i < cnt; i++) {
730       RowData &rd = mData[i];
731 
732       lt->AddLabel(rd.selectedRegion, rd.title);
733    }
734 
735    // Export them and clean
736    lt->Export(f);
737 
738 #ifdef __WXMAC__
739    f.Write(wxTextFileType_Mac);
740 #else
741    f.Write();
742 #endif
743    f.Close();
744 }
745 
OnSelectCell(wxGridEvent & event)746 void LabelDialog::OnSelectCell(wxGridEvent &event)
747 {
748    for (auto t: mTracks->Any())
749       t->SetSelected( true );
750 
751    if (!mData.empty())
752    {
753       RowData &rd = mData[event.GetRow()];
754       mViewInfo->selectedRegion = rd.selectedRegion;
755 
756       ProjectWindow::Get( mProject ).RedrawProject();
757    }
758 
759    event.Skip();
760 }
761 
OnCellChange(wxGridEvent & event)762 void LabelDialog::OnCellChange(wxGridEvent &event)
763 {
764    static bool guard = false;
765    int row = event.GetRow();
766 
767    // Guard against recursion which can happen when a change to the "NEW label" row
768    // is made.  When InsertRow() is done in TransferDataToWindow(), checks are made
769    // within wxGrid to see if the edit control is active and since it hasn't yet
770    // been marked inactive on the first time through here, we get entered again.
771    // Sort of a double change.  I think this is probably a bug in wxGrid.
772    if (guard) {
773       return;
774    }
775    guard = true;
776 
777    // The change was to an existing label, so go process it based
778    // on which column was changed.
779    RowData *rd = &mData[row];
780    switch (event.GetCol())
781    {
782       case Col_Track:
783          OnChangeTrack(event, row, rd);
784       break;
785 
786       case Col_Label:
787          OnChangeLabel(event, row, rd);
788       break;
789 
790       case Col_Stime:
791          OnChangeStime(event, row, rd);
792       break;
793 
794       case Col_Etime:
795          OnChangeEtime(event, row, rd);
796       break;
797 
798       case Col_Lfreq:
799          OnChangeLfreq(event, row, rd);
800       break;
801 
802       case Col_Hfreq:
803          OnChangeHfreq(event, row, rd);
804       break;
805    }
806 
807    // Done...no need for protection anymore
808    guard = false;
809 
810    return;
811 }
812 
OnChangeTrack(wxGridEvent & WXUNUSED (event),int row,RowData * rd)813 void LabelDialog::OnChangeTrack(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
814 {
815    wxString val = mGrid->GetCellValue(row, Col_Track);
816 
817    // User selected the "New..." choice so ask for a NEW name
818    if ( make_iterator_range( mTrackNames ).index( val ) == 0 ) {
819       AudacityTextEntryDialog d(this,
820          XO("New Label Track"),
821          XO("Enter track name"),
822          /* i18n-hint: (noun) it's the name of a kind of track.*/
823          XO("Label Track").Translation());
824 
825       // User canceled so repopulating the grid will set the track
826       // name to the original value
827       if (d.ShowModal() == wxID_CANCEL) {
828          TransferDataToWindow();
829          return;
830       }
831 
832       // Force generation of a NEW track name
833       rd->index = 0;
834       TrackName(rd->index, d.GetValue());
835    }
836    else {
837       // Remember the tracks index
838       rd->index = make_iterator_range( mTrackNames ).index( val );
839    }
840 
841    // Repopulate the grid
842    TransferDataToWindow();
843 
844    return;
845 }
846 
OnChangeLabel(wxGridEvent & WXUNUSED (event),int row,RowData * rd)847 void LabelDialog::OnChangeLabel(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
848 {
849    // Remember the value...no need to repopulate
850    rd->title = mGrid->GetCellValue(row, Col_Label);
851 
852    return;
853 }
854 
OnChangeStime(wxGridEvent & WXUNUSED (event),int row,RowData * rd)855 void LabelDialog::OnChangeStime(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
856 {
857    // Remember the value...no need to repopulate
858    double t {};
859    mGrid->GetCellValue(row, Col_Stime).ToDouble(&t);
860    rd->selectedRegion.setT0(t, false);
861    mGrid->SetCellValue(row, Col_Etime, wxString::Format(wxT("%g"),
862                        rd->selectedRegion.t1()));
863 
864    return;
865 }
866 
OnChangeEtime(wxGridEvent & WXUNUSED (event),int row,RowData * rd)867 void LabelDialog::OnChangeEtime(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
868 {
869    // Remember the value...no need to repopulate
870    double t {};
871    mGrid->GetCellValue(row, Col_Etime).ToDouble(&t);
872    rd->selectedRegion.setT1(t, false);
873    mGrid->SetCellValue(row, Col_Stime, wxString::Format(wxT("%g"),
874                        rd->selectedRegion.t0()));
875 
876    return;
877 }
878 
OnChangeLfreq(wxGridEvent & WXUNUSED (event),int row,RowData * rd)879 void LabelDialog::OnChangeLfreq(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
880 {
881    // Remember the value...no need to repopulate
882    double f;
883    mGrid->GetCellValue(row, Col_Lfreq).ToDouble(&f);
884    rd->selectedRegion.setF0(f, false);
885    mGrid->SetCellValue(row, Col_Hfreq, wxString::Format(wxT("%g"),
886                                                         rd->selectedRegion.f1()));
887 
888    return;
889 }
890 
OnChangeHfreq(wxGridEvent & WXUNUSED (event),int row,RowData * rd)891 void LabelDialog::OnChangeHfreq(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
892 {
893    // Remember the value...no need to repopulate
894    double f;
895    mGrid->GetCellValue(row, Col_Hfreq).ToDouble(&f);
896    rd->selectedRegion.setF1(f, false);
897    mGrid->SetCellValue(row, Col_Lfreq, wxString::Format(wxT("%g"),
898                                                         rd->selectedRegion.f0()));
899 
900    return;
901 }
902 
ReadSize()903 void LabelDialog::ReadSize(){
904    wxSize sz = GetSize();
905    int prefWidth, prefHeight;
906    gPrefs->Read(wxT("/LabelEditor/Width"), &prefWidth, sz.x);
907    gPrefs->Read(wxT("/LabelEditor/Height"), &prefHeight, sz.y);
908 
909    wxRect screenRect(wxGetClientDisplayRect());
910    wxSize prefSize = wxSize(prefWidth, prefHeight);
911    prefSize.DecTo(screenRect.GetSize());
912    SetSize(prefSize);
913 }
914 
WriteSize()915 void LabelDialog::WriteSize(){
916    wxSize sz = GetSize();
917    gPrefs->Write(wxT("/LabelEditor/Width"), sz.x);
918    gPrefs->Write(wxT("/LabelEditor/Height"), sz.y);
919    gPrefs->Flush();
920 }
921 
OnOK(wxCommandEvent & WXUNUSED (event))922 void LabelDialog::OnOK(wxCommandEvent & WXUNUSED(event))
923 {
924    if (mGrid->IsCellEditControlShown()) {
925       mGrid->SaveEditControlValue();
926       mGrid->HideCellEditControl();
927       return;
928    }
929 
930    // Standard handling
931    if (Validate() && TransferDataFromWindow()) {
932       WriteSize();
933       EndModal(wxID_OK);
934    }
935 
936    return;
937 }
938 
OnCancel(wxCommandEvent & WXUNUSED (event))939 void LabelDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
940 {
941    if (mGrid->IsCellEditControlShown()) {
942       auto editor = mGrid->GetCellEditor(mGrid->GetGridCursorRow(),
943          mGrid->GetGridCursorCol());
944       editor->Reset();
945       // To avoid memory leak, don't forget DecRef()!
946       editor->DecRef();
947       mGrid->HideCellEditControl();
948       return;
949    }
950 
951    WriteSize();
952    // Standard handling
953    EndModal(wxID_CANCEL);
954 
955    return;
956 }
957