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