mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-06 23:02:42 +02:00
956 lines
25 KiB
C++
956 lines
25 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
LabelDialog.cpp
|
|
|
|
Dominic Mazzoni
|
|
|
|
*******************************************************************//**
|
|
|
|
\class LabelDialog
|
|
\brief Dialog for editing labels.
|
|
|
|
*//*******************************************************************/
|
|
|
|
#include "Audacity.h"
|
|
#include "LabelDialog.h"
|
|
|
|
#include <wx/button.h>
|
|
#include <wx/defs.h>
|
|
#include <wx/choice.h>
|
|
#include <wx/dc.h>
|
|
#include <wx/dialog.h>
|
|
#include <wx/grid.h>
|
|
#include <wx/intl.h>
|
|
#include <wx/scrolbar.h>
|
|
#include <wx/settings.h>
|
|
#include <wx/sizer.h>
|
|
#include <wx/stattext.h>
|
|
#include <wx/textdlg.h>
|
|
|
|
#include "ShuttleGui.h"
|
|
#include "LabelTrack.h"
|
|
#include "Prefs.h"
|
|
#include "Project.h"
|
|
#include "ProjectWindow.h"
|
|
#include "ViewInfo.h"
|
|
#include "tracks/labeltrack/ui/LabelTrackView.h"
|
|
#include "widgets/AudacityMessageBox.h"
|
|
#include "widgets/ErrorDialog.h"
|
|
#include "widgets/Grid.h"
|
|
#include "widgets/HelpSystem.h"
|
|
|
|
#include "FileNames.h"
|
|
#include <limits>
|
|
|
|
enum Column
|
|
{
|
|
Col_Track,
|
|
Col_Label,
|
|
Col_Stime,
|
|
Col_Etime,
|
|
Col_Lfreq,
|
|
Col_Hfreq,
|
|
Col_Max
|
|
};
|
|
|
|
|
|
|
|
class RowData
|
|
{
|
|
public:
|
|
RowData(int index_, const wxString &title_, const SelectedRegion &selectedRegion_)
|
|
: index(index_), title(title_), selectedRegion(selectedRegion_)
|
|
{}
|
|
|
|
int index;
|
|
wxString title;
|
|
SelectedRegion selectedRegion;
|
|
};
|
|
|
|
enum {
|
|
ID_INSERTA = 11000,
|
|
ID_INSERTB,
|
|
ID_REMOVE,
|
|
ID_IMPORT,
|
|
ID_EXPORT
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(LabelDialog, wxDialogWrapper)
|
|
EVT_GRID_SELECT_CELL(LabelDialog::OnSelectCell)
|
|
EVT_GRID_CELL_CHANGED(LabelDialog::OnCellChange)
|
|
EVT_BUTTON(ID_INSERTA, LabelDialog::OnInsert)
|
|
EVT_BUTTON(ID_INSERTB, LabelDialog::OnInsert)
|
|
EVT_BUTTON(ID_REMOVE, LabelDialog::OnRemove)
|
|
EVT_BUTTON(ID_IMPORT, LabelDialog::OnImport)
|
|
EVT_BUTTON(ID_EXPORT, LabelDialog::OnExport)
|
|
EVT_BUTTON(wxID_OK, LabelDialog::OnOK)
|
|
EVT_BUTTON(wxID_CANCEL, LabelDialog::OnCancel)
|
|
EVT_COMMAND(wxID_ANY, EVT_TIMETEXTCTRL_UPDATED, LabelDialog::OnUpdate)
|
|
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED,
|
|
LabelDialog::OnFreqUpdate)
|
|
EVT_BUTTON(wxID_HELP, LabelDialog::OnHelp)
|
|
END_EVENT_TABLE()
|
|
|
|
LabelDialog::LabelDialog(wxWindow *parent,
|
|
AudacityProject &project,
|
|
TrackList *tracks,
|
|
LabelTrack *selectedTrack,
|
|
int index,
|
|
ViewInfo &viewinfo,
|
|
double rate,
|
|
const NumericFormatSymbol & format,
|
|
const NumericFormatSymbol &freqFormat)
|
|
: wxDialogWrapper(parent,
|
|
wxID_ANY,
|
|
XO("Edit Labels"),
|
|
wxDefaultPosition,
|
|
wxSize(800, 600),
|
|
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
|
, mProject{ project }
|
|
, mTracks(tracks)
|
|
, mSelectedTrack(selectedTrack)
|
|
, mIndex(index)
|
|
, mViewInfo(&viewinfo),
|
|
mRate(rate),
|
|
mFormat(format)
|
|
, mFreqFormat(freqFormat)
|
|
{
|
|
SetName();
|
|
Populate();
|
|
}
|
|
|
|
LabelDialog::~LabelDialog()
|
|
{
|
|
}
|
|
|
|
void LabelDialog::PopulateLabels()
|
|
{
|
|
// Build the initial (empty) grid
|
|
mGrid->CreateGrid(0, Col_Max, wxGrid::wxGridSelectRows);
|
|
mGrid->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTER);
|
|
mGrid->SetRowLabelSize(0);
|
|
|
|
size_t ii = 0;
|
|
for ( const auto &label : {
|
|
/* i18n-hint: (noun). A track contains waves, audio etc.*/
|
|
XO("Track"),
|
|
/* i18n-hint: (noun)*/
|
|
XO("Label"),
|
|
/* i18n-hint: (noun) of a label*/
|
|
XO("Start Time"),
|
|
/* i18n-hint: (noun) of a label*/
|
|
XO("End Time"),
|
|
/* i18n-hint: (noun) of a label*/
|
|
XO("Low Frequency"),
|
|
/* i18n-hint: (noun) of a label*/
|
|
XO("High Frequency"),
|
|
})
|
|
mGrid->SetColLabelValue( ii++, label.Translation() );
|
|
|
|
// Create and remember editors. No need to DELETE these as the wxGrid will
|
|
// do it for us. (The DecRef() that is needed after GetDefaultEditorForType
|
|
// becomes the duty of the wxGridCellAttr objects after we set them in the grid.)
|
|
mChoiceEditor = (ChoiceEditor *) mGrid->GetDefaultEditorForType(GRID_VALUE_CHOICE);
|
|
mTimeEditor = static_cast<NumericEditor*>
|
|
(mGrid->GetDefaultEditorForType(GRID_VALUE_TIME));
|
|
mFrequencyEditor = static_cast<NumericEditor *>
|
|
(mGrid->GetDefaultEditorForType(GRID_VALUE_FREQUENCY));
|
|
|
|
// Initialize and set the track name column attributes
|
|
wxGridCellAttr *attr;
|
|
mGrid->SetColAttr(Col_Track, (attr = safenew wxGridCellAttr));
|
|
attr->SetEditor(mChoiceEditor);
|
|
mTrackNames.push_back(_("New..."));
|
|
|
|
// Initialize and set the time column attributes
|
|
mGrid->SetColAttr(Col_Stime, (attr = safenew wxGridCellAttr));
|
|
// Don't need DecRef() after this GetDefaultRendererForType.
|
|
attr->SetRenderer(mGrid->GetDefaultRendererForType(GRID_VALUE_TIME));
|
|
attr->SetEditor(mTimeEditor);
|
|
attr->SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
|
|
|
|
mGrid->SetColAttr(Col_Etime, attr->Clone());
|
|
|
|
// Initialize and set the frequency column attributes
|
|
mGrid->SetColAttr(Col_Lfreq, (attr = safenew wxGridCellAttr));
|
|
// Don't need DecRef() after this GetDefaultRendererForType.
|
|
attr->SetRenderer(mGrid->GetDefaultRendererForType(GRID_VALUE_FREQUENCY));
|
|
attr->SetEditor(mFrequencyEditor);
|
|
attr->SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
|
|
|
|
mGrid->SetColAttr(Col_Hfreq, attr->Clone());
|
|
|
|
// Seems there's a bug in wxGrid. Adding only 1 row does not
|
|
// allow SetCellSize() to work properly and you will not get
|
|
// the expected 1 row by 4 column cell.
|
|
//
|
|
// So, we set the minimum row height to 0 and basically hide
|
|
// the extra row by setting its height to 0. And not allowing the
|
|
// rows to be manually resized prevents the user from ever seeing
|
|
// the extra row.
|
|
mGrid->SetRowMinimalAcceptableHeight(0);
|
|
mGrid->EnableDragRowSize(false);
|
|
|
|
// Locate all labels in current track list
|
|
FindAllLabels();
|
|
|
|
// Populate the grid
|
|
TransferDataToWindow();
|
|
|
|
// Resize the label name column and ensure it doesn't go below an
|
|
// arbitrary width.
|
|
//
|
|
// This should not be in TransferDataToWindow() since a user might
|
|
// resize the column and we'd resize it back to the minimum.
|
|
mGrid->AutoSizeColumn(Col_Label, false );
|
|
mGrid->SetColSize(Col_Label, wxMax(150, mGrid->GetColSize(Col_Label)));
|
|
mGrid->SetColMinimalWidth(Col_Label, mGrid->GetColSize(Col_Label));
|
|
|
|
}
|
|
|
|
|
|
/// Creates the dialog and its contents.
|
|
void LabelDialog::Populate()
|
|
{
|
|
|
|
//------------------------- Main section --------------------
|
|
ShuttleGui S(this, eIsCreating);
|
|
PopulateOrExchange(S);
|
|
// ----------------------- End of main section --------------
|
|
|
|
// Go populate the macros list.
|
|
PopulateLabels();
|
|
|
|
// Layout the works
|
|
Layout();
|
|
//Fit();
|
|
|
|
// Resize width based on width of columns and the vertical scrollbar
|
|
wxRect r = mGrid->GetGridColLabelWindow()->GetRect();
|
|
wxScrollBar sb(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL);
|
|
r.width += sb.GetSize().GetWidth() + 6;
|
|
|
|
// Add the size of the right column of buttons too...
|
|
wxWindow * w = FindWindowById( ID_IMPORT, this );
|
|
wxASSERT( w );
|
|
if( w )
|
|
r.width += w->GetSize().GetWidth();
|
|
|
|
SetClientSize(r.width, 300);
|
|
|
|
// Make sure it doesn't go below this size
|
|
r = GetRect();
|
|
SetSizeHints(r.GetWidth(), r.GetHeight());
|
|
|
|
// Bug 1465
|
|
// There might be a saved size, in which case use that.
|
|
ReadSize();
|
|
|
|
// Center on display
|
|
Center();
|
|
}
|
|
|
|
void LabelDialog::PopulateOrExchange( ShuttleGui & S )
|
|
{
|
|
S.AddFixedText(XO("Press F2 or double click to edit cell contents."));
|
|
S.StartHorizontalLay(wxEXPAND,1);
|
|
{
|
|
S.StartVerticalLay(wxEXPAND,1);
|
|
{
|
|
mGrid = safenew Grid(S.GetParent(), wxID_ANY);
|
|
S.Prop(1).AddWindow( mGrid );
|
|
}
|
|
S.EndVerticalLay();
|
|
S.StartVerticalLay(0);
|
|
{
|
|
//S.Id(ID_INSERTA).AddButton(XO("&Insert"), wxALIGN_LEFT);
|
|
S.Id(ID_INSERTB).AddButton(XXO("&Insert"), wxALIGN_LEFT);
|
|
//S.Id(EditButtonID).AddButton(XO("&Edit"), wxALIGN_LEFT);
|
|
S.Id(ID_REMOVE).AddButton(XXO("De&lete"), wxALIGN_LEFT);
|
|
S.Id(ID_IMPORT).AddButton(XXO("I&mport..."), wxALIGN_LEFT);
|
|
S.Id(ID_EXPORT).AddButton(XXO("&Export..."), wxALIGN_LEFT);
|
|
}
|
|
S.EndVerticalLay();
|
|
}
|
|
S.EndHorizontalLay();
|
|
|
|
S.StartHorizontalLay(wxALIGN_RIGHT, false);
|
|
{
|
|
S.AddStandardButtons( eOkButton | eCancelButton | eHelpButton);
|
|
}
|
|
S.EndHorizontalLay();
|
|
}
|
|
|
|
void LabelDialog::OnHelp(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
wxString page = GetHelpPageName();
|
|
HelpSystem::ShowHelp(this, page, true);
|
|
}
|
|
|
|
|
|
bool LabelDialog::TransferDataToWindow()
|
|
{
|
|
int cnt = mData.size();
|
|
int i;
|
|
|
|
// Set the editor parameters. Do this each time since they may change
|
|
// due to NEW tracks and change in NumericTextCtrl format. Rate won't
|
|
// change but might as well leave it here.
|
|
mChoiceEditor->SetChoices(mTrackNames);
|
|
mTimeEditor->SetFormat(mFormat);
|
|
mTimeEditor->SetRate(mRate);
|
|
mFrequencyEditor->SetFormat(mFreqFormat);
|
|
mFrequencyEditor->SetRate(mRate);
|
|
|
|
// Disable redrawing until we're done
|
|
mGrid->BeginBatch();
|
|
|
|
// Delete all rows
|
|
if (mGrid->GetNumberRows()) {
|
|
mGrid->DeleteRows(0, mGrid->GetNumberRows());
|
|
}
|
|
|
|
// Add the exact number that we'll need
|
|
mGrid->InsertRows(0, cnt);
|
|
|
|
// Populate the rows
|
|
for (i = 0; i < cnt; i++) {
|
|
RowData &rd = mData[i];
|
|
|
|
// Set the cell contents
|
|
mGrid->SetCellValue(i, Col_Track, TrackName(rd.index));
|
|
mGrid->SetCellValue(i, Col_Label, rd.title);
|
|
mGrid->SetCellValue(i, Col_Stime,
|
|
wxString::Format(wxT("%g"), rd.selectedRegion.t0()));
|
|
mGrid->SetCellValue(i, Col_Etime,
|
|
wxString::Format(wxT("%g"), rd.selectedRegion.t1()));
|
|
mGrid->SetCellValue(i, Col_Lfreq,
|
|
wxString::Format(wxT("%g"), rd.selectedRegion.f0()));
|
|
mGrid->SetCellValue(i, Col_Hfreq,
|
|
wxString::Format(wxT("%g"), rd.selectedRegion.f1()));
|
|
}
|
|
|
|
// Autosize all the rows
|
|
mGrid->AutoSizeRows(true);
|
|
|
|
// Resize the track name column. Use a wxChoice to determine the maximum
|
|
// width needed.
|
|
wxChoice tc(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, mTrackNames);
|
|
mGrid->SetColSize(Col_Track, tc.GetSize().x);
|
|
mGrid->SetColMinimalWidth(Col_Track, tc.GetSize().x);
|
|
|
|
// Autosize the time columns and set their minimal widths
|
|
mGrid->AutoSizeColumn(Col_Stime);
|
|
mGrid->AutoSizeColumn(Col_Etime);
|
|
mGrid->AutoSizeColumn(Col_Lfreq);
|
|
mGrid->AutoSizeColumn(Col_Hfreq);
|
|
|
|
// We're done, so allow the grid to redraw
|
|
mGrid->EndBatch();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LabelDialog::Show(bool show)
|
|
{
|
|
bool ret = wxDialogWrapper::Show(show);
|
|
|
|
#if defined(__WXMAC__) || defined(__WXGTK__)
|
|
if (show) {
|
|
mGrid->SetFocus(); // Required for Linux and Mac.
|
|
}
|
|
#endif
|
|
|
|
// Set initial row
|
|
// (This will not work until the grid is actually displayed)
|
|
if (show && mInitialRow != -1) {
|
|
mGrid->GoToCell(mInitialRow, Col_Label);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool LabelDialog::TransferDataFromWindow()
|
|
{
|
|
int cnt = mData.size();
|
|
int i;
|
|
int tndx = 0;
|
|
|
|
// Clear label tracks of labels
|
|
for (auto lt : mTracks->Any<LabelTrack>()) {
|
|
++tndx;
|
|
if (!mSelectedTrack) {
|
|
for (i = lt->GetNumLabels() - 1; i >= 0 ; i--) {
|
|
lt->DeleteLabel(i);
|
|
}
|
|
}
|
|
else if (mSelectedTrack == lt && mIndex > -1) {
|
|
lt->DeleteLabel(mIndex);
|
|
}
|
|
else
|
|
// Do nothing to the nonselected tracks
|
|
;
|
|
}
|
|
|
|
// Create any added tracks
|
|
while (tndx < (int)mTrackNames.size() - 1) {
|
|
|
|
// Extract the name
|
|
wxString name = mTrackNames[tndx + 1].AfterFirst(wxT('-')).Mid(1);
|
|
|
|
// Create the NEW track and add to track list
|
|
auto newTrack = std::make_shared<LabelTrack>();
|
|
newTrack->SetName(name);
|
|
mTracks->Add( newTrack );
|
|
tndx++;
|
|
}
|
|
|
|
// Repopulate with updated labels
|
|
for (i = 0; i < cnt; i++) {
|
|
RowData &rd = mData[i];
|
|
|
|
// Look for track with matching index
|
|
tndx = 1;
|
|
LabelTrack *lt{};
|
|
for (auto t : mTracks->Any<LabelTrack>()) {
|
|
lt = t;
|
|
if (rd.index == tndx++) {
|
|
break;
|
|
}
|
|
}
|
|
wxASSERT(lt);
|
|
if (!lt)
|
|
return false;
|
|
|
|
// Add the label to it
|
|
lt->AddLabel(rd.selectedRegion, rd.title);
|
|
LabelTrackView::Get( *lt ).SetSelectedIndex( -1 );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LabelDialog::Validate()
|
|
{
|
|
if (mGrid->IsCellEditControlShown()) {
|
|
mGrid->HideCellEditControl();
|
|
mGrid->SaveEditControlValue();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
wxString LabelDialog::TrackName(int & index, const wxString &dflt)
|
|
{
|
|
// Generate a NEW track name if the passed index is out of range
|
|
if (index < 1 || index >= (int)mTrackNames.size()) {
|
|
index = mTrackNames.size();
|
|
mTrackNames.push_back(wxString::Format(wxT("%d - %s"), index, dflt));
|
|
}
|
|
|
|
// Return the track name
|
|
return mTrackNames[index];
|
|
}
|
|
|
|
void LabelDialog::FindAllLabels()
|
|
{
|
|
// Add labels from all label tracks
|
|
for (auto lt : mTracks->Any<const LabelTrack>()) {
|
|
AddLabels(lt);
|
|
}
|
|
|
|
FindInitialRow();
|
|
|
|
if (mData.size() == 0) {
|
|
wxCommandEvent e;
|
|
OnInsert(e);
|
|
}
|
|
}
|
|
|
|
void LabelDialog::AddLabels(const LabelTrack *t)
|
|
{
|
|
wxString lab;
|
|
int tndx = 0;
|
|
int i;
|
|
|
|
// Add a NEW track name
|
|
TrackName(tndx, t->GetName());
|
|
|
|
// If editor was invoked for one label, add that one only, else add all.
|
|
if (!mSelectedTrack || mSelectedTrack == t) {
|
|
for (i = 0; i < t->GetNumLabels(); i++) {
|
|
const LabelStruct *ls = t->GetLabel(i);
|
|
|
|
if (mIndex < 0 || mIndex == i)
|
|
mData.push_back(RowData(tndx, ls->title, ls->selectedRegion));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabelDialog::FindInitialRow()
|
|
{
|
|
int cnt = mData.size();
|
|
mInitialRow = -1;
|
|
|
|
if (cnt == 0)
|
|
return;
|
|
|
|
// find closest previous label
|
|
|
|
double distMin = std::numeric_limits<double>::max();
|
|
double dist;
|
|
double t0 = mViewInfo->selectedRegion.t0();
|
|
int i;
|
|
for (i = 0; i < cnt; i++)
|
|
{
|
|
dist = t0 - mData[i].selectedRegion.t0();
|
|
if (dist >= 0.0 && dist < distMin)
|
|
{
|
|
mInitialRow = i;
|
|
distMin = dist;
|
|
}
|
|
}
|
|
|
|
// if no previous label was found, find first label
|
|
|
|
if (mInitialRow == -1)
|
|
{
|
|
double t0Min = std::numeric_limits<double>::max();
|
|
for (i = 0; i < cnt; i++)
|
|
{
|
|
if (mData[i].selectedRegion.t0() < t0Min)
|
|
{
|
|
mInitialRow = i;
|
|
t0Min = mData[i].selectedRegion.t0();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabelDialog::OnUpdate(wxCommandEvent &event)
|
|
{
|
|
// Remember the NEW format and repopulate grid
|
|
mFormat = NumericConverter::LookupFormat(
|
|
NumericConverter::TIME, event.GetString() );
|
|
TransferDataToWindow();
|
|
|
|
event.Skip(false);
|
|
}
|
|
|
|
void LabelDialog::OnFreqUpdate(wxCommandEvent &event)
|
|
{
|
|
// Remember the NEW format and repopulate grid
|
|
mFreqFormat = NumericConverter::LookupFormat(
|
|
NumericConverter::FREQUENCY, event.GetString() );
|
|
TransferDataToWindow();
|
|
|
|
event.Skip(false);
|
|
}
|
|
|
|
void LabelDialog::OnInsert(wxCommandEvent &event)
|
|
{
|
|
int cnt = mData.size();
|
|
int row = 0;
|
|
int index = 0;
|
|
|
|
// Make sure the edit control isn't active before inserting any rows
|
|
if (mGrid->IsCellEditControlShown()) {
|
|
mGrid->HideCellEditControl();
|
|
}
|
|
|
|
// Attempt to guess which track the label should reside on
|
|
if (cnt > 0) {
|
|
row = mGrid->GetGridCursorRow();
|
|
if (row > 0 && row >= cnt) {
|
|
index = make_iterator_range( mTrackNames )
|
|
.index( mGrid->GetCellValue(row - 1, Col_Track) );
|
|
}
|
|
else {
|
|
index = make_iterator_range( mTrackNames )
|
|
.index( mGrid->GetCellValue(row, Col_Track) );
|
|
}
|
|
}
|
|
|
|
// Insert NEW label before or after the current row
|
|
if (event.GetId() == ID_INSERTA && row < cnt) {
|
|
row++;
|
|
}
|
|
mData.insert(mData.begin() + row, RowData(index, wxT(""), SelectedRegion()));
|
|
|
|
// Repopulate the grid
|
|
TransferDataToWindow();
|
|
|
|
// Reposition cursor to NEW row/col and put user into edit mode to
|
|
// set the label name
|
|
mGrid->SetGridCursor(row, Col_Label);
|
|
mGrid->EnableCellEditControl(true);
|
|
mGrid->ShowCellEditControl();
|
|
}
|
|
|
|
void LabelDialog::OnRemove(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
int row = mGrid->GetGridCursorRow();
|
|
int col = mGrid->GetGridCursorCol();
|
|
int cnt = mData.size();
|
|
|
|
// Don't try to remove if no labels exist
|
|
if (cnt == 0) {
|
|
return;
|
|
}
|
|
|
|
// Make sure the edit control isn't active before removing rows
|
|
if (mGrid->IsCellEditControlShown()) {
|
|
mGrid->HideCellEditControl();
|
|
}
|
|
|
|
// Remove the row
|
|
//RowData &rd = mData[row];
|
|
mData.erase(mData.begin() + row);
|
|
|
|
// Repopulate the grid
|
|
TransferDataToWindow();
|
|
|
|
// Reposition the cursor
|
|
if (row > 0 && row >= --cnt) {
|
|
row--;
|
|
}
|
|
mGrid->SetGridCursor(row, col);
|
|
|
|
// Make sure focus isn't lost
|
|
if (mData.size() == 0 && wxWindow::FindFocus() == mGrid->GetGridWindow()) {
|
|
wxWindow *ok = wxWindow::FindWindowById( wxID_OK, this);
|
|
if (ok) {
|
|
ok->SetFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabelDialog::OnImport(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
// Ask user for a filename
|
|
wxString fileName =
|
|
FileNames::SelectFile(FileNames::Operation::Open,
|
|
XO("Select a text file containing labels"),
|
|
wxEmptyString, // Path
|
|
wxT(""), // Name
|
|
wxT("txt"), // Extension
|
|
{ FileNames::TextFiles, FileNames::AllFiles },
|
|
wxRESIZE_BORDER, // Flags
|
|
this); // Parent
|
|
|
|
// They gave us one...
|
|
if (!fileName.empty()) {
|
|
wxTextFile f;
|
|
|
|
// Get at the data
|
|
f.Open(fileName);
|
|
if (!f.IsOpened()) {
|
|
AudacityMessageBox(
|
|
XO("Could not open file: %s").Format( fileName ) );
|
|
}
|
|
else {
|
|
// Create a temporary label track and load the labels
|
|
// into it
|
|
auto lt = std::make_shared<LabelTrack>();
|
|
lt->Import(f);
|
|
|
|
// Add the labels to our collection
|
|
AddLabels(lt.get());
|
|
|
|
// Done with the temporary track
|
|
}
|
|
|
|
// Repopulate the grid
|
|
TransferDataToWindow();
|
|
}
|
|
}
|
|
|
|
void LabelDialog::OnExport(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
int cnt = mData.size();
|
|
|
|
// Silly user (could just disable the button, but that's a hassle ;-))
|
|
if (cnt == 0) {
|
|
AudacityMessageBox( XO("No labels to export.") );
|
|
return;
|
|
}
|
|
|
|
// Extract the actual name.
|
|
wxString fName = mTrackNames[mTrackNames.size() - 1].AfterFirst(wxT('-')).Mid(1);
|
|
|
|
fName = FileNames::SelectFile(FileNames::Operation::Export,
|
|
XO("Export Labels As:"),
|
|
wxEmptyString,
|
|
fName,
|
|
wxT("txt"),
|
|
{ FileNames::TextFiles },
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
|
|
this);
|
|
|
|
if (fName.empty())
|
|
return;
|
|
|
|
// Move existing files out of the way. Otherwise wxTextFile will
|
|
// append to (rather than replace) the current file.
|
|
|
|
if (wxFileExists(fName)) {
|
|
#ifdef __WXGTK__
|
|
wxString safetyFileName = fName + wxT("~");
|
|
#else
|
|
wxString safetyFileName = fName + wxT(".bak");
|
|
#endif
|
|
|
|
if (wxFileExists(safetyFileName))
|
|
wxRemoveFile(safetyFileName);
|
|
|
|
wxRename(fName, safetyFileName);
|
|
}
|
|
|
|
wxTextFile f(fName);
|
|
#ifdef __WXMAC__
|
|
wxFile{}.Create(fName);
|
|
#else
|
|
f.Create();
|
|
#endif
|
|
f.Open();
|
|
if (!f.IsOpened()) {
|
|
AudacityMessageBox(
|
|
XO("Couldn't write to file: %s").Format( fName ) );
|
|
return;
|
|
}
|
|
|
|
// Transfer our collection to a temporary label track
|
|
auto lt = std::make_shared<LabelTrack>();
|
|
int i;
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
RowData &rd = mData[i];
|
|
|
|
lt->AddLabel(rd.selectedRegion, rd.title);
|
|
}
|
|
|
|
// Export them and clean
|
|
lt->Export(f);
|
|
|
|
#ifdef __WXMAC__
|
|
f.Write(wxTextFileType_Mac);
|
|
#else
|
|
f.Write();
|
|
#endif
|
|
f.Close();
|
|
}
|
|
|
|
void LabelDialog::OnSelectCell(wxGridEvent &event)
|
|
{
|
|
for (auto t: mTracks->Any())
|
|
t->SetSelected( true );
|
|
|
|
if (!mData.empty())
|
|
{
|
|
RowData &rd = mData[event.GetRow()];
|
|
mViewInfo->selectedRegion = rd.selectedRegion;
|
|
|
|
ProjectWindow::Get( mProject ).RedrawProject();
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void LabelDialog::OnCellChange(wxGridEvent &event)
|
|
{
|
|
static bool guard = false;
|
|
int row = event.GetRow();
|
|
|
|
// Guard against recursion which can happen when a change to the "NEW label" row
|
|
// is made. When InsertRow() is done in TransferDataToWindow(), checks are made
|
|
// within wxGrid to see if the edit control is active and since it hasn't yet
|
|
// been marked inactive on the first time through here, we get entered again.
|
|
// Sort of a double change. I think this is probably a bug in wxGrid.
|
|
if (guard) {
|
|
return;
|
|
}
|
|
guard = true;
|
|
|
|
// The change was to an existing label, so go process it based
|
|
// on which column was changed.
|
|
RowData *rd = &mData[row];
|
|
switch (event.GetCol())
|
|
{
|
|
case Col_Track:
|
|
OnChangeTrack(event, row, rd);
|
|
break;
|
|
|
|
case Col_Label:
|
|
OnChangeLabel(event, row, rd);
|
|
break;
|
|
|
|
case Col_Stime:
|
|
OnChangeStime(event, row, rd);
|
|
break;
|
|
|
|
case Col_Etime:
|
|
OnChangeEtime(event, row, rd);
|
|
break;
|
|
|
|
case Col_Lfreq:
|
|
OnChangeLfreq(event, row, rd);
|
|
break;
|
|
|
|
case Col_Hfreq:
|
|
OnChangeHfreq(event, row, rd);
|
|
break;
|
|
}
|
|
|
|
// Done...no need for protection anymore
|
|
guard = false;
|
|
|
|
return;
|
|
}
|
|
|
|
void LabelDialog::OnChangeTrack(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
|
|
{
|
|
wxString val = mGrid->GetCellValue(row, Col_Track);
|
|
|
|
// User selected the "New..." choice so ask for a NEW name
|
|
if ( make_iterator_range( mTrackNames ).index( val ) == 0 ) {
|
|
AudacityTextEntryDialog d(this,
|
|
XO("New Label Track"),
|
|
XO("Enter track name"),
|
|
/* i18n-hint: (noun) it's the name of a kind of track.*/
|
|
XO("Label Track").Translation());
|
|
|
|
// User canceled so repopulating the grid will set the track
|
|
// name to the original value
|
|
if (d.ShowModal() == wxID_CANCEL) {
|
|
TransferDataToWindow();
|
|
return;
|
|
}
|
|
|
|
// Force generation of a NEW track name
|
|
rd->index = 0;
|
|
TrackName(rd->index, d.GetValue());
|
|
}
|
|
else {
|
|
// Remember the tracks index
|
|
rd->index = make_iterator_range( mTrackNames ).index( val );
|
|
}
|
|
|
|
// Repopulate the grid
|
|
TransferDataToWindow();
|
|
|
|
return;
|
|
}
|
|
|
|
void LabelDialog::OnChangeLabel(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
|
|
{
|
|
// Remember the value...no need to repopulate
|
|
rd->title = mGrid->GetCellValue(row, Col_Label);
|
|
|
|
return;
|
|
}
|
|
|
|
void LabelDialog::OnChangeStime(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
|
|
{
|
|
// Remember the value...no need to repopulate
|
|
double t {};
|
|
mGrid->GetCellValue(row, Col_Stime).ToDouble(&t);
|
|
rd->selectedRegion.setT0(t, false);
|
|
mGrid->SetCellValue(row, Col_Etime, wxString::Format(wxT("%g"),
|
|
rd->selectedRegion.t1()));
|
|
|
|
return;
|
|
}
|
|
|
|
void LabelDialog::OnChangeEtime(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
|
|
{
|
|
// Remember the value...no need to repopulate
|
|
double t {};
|
|
mGrid->GetCellValue(row, Col_Etime).ToDouble(&t);
|
|
rd->selectedRegion.setT1(t, false);
|
|
mGrid->SetCellValue(row, Col_Stime, wxString::Format(wxT("%g"),
|
|
rd->selectedRegion.t0()));
|
|
|
|
return;
|
|
}
|
|
|
|
void LabelDialog::OnChangeLfreq(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
|
|
{
|
|
// Remember the value...no need to repopulate
|
|
double f;
|
|
mGrid->GetCellValue(row, Col_Lfreq).ToDouble(&f);
|
|
rd->selectedRegion.setF0(f, false);
|
|
mGrid->SetCellValue(row, Col_Hfreq, wxString::Format(wxT("%g"),
|
|
rd->selectedRegion.f1()));
|
|
|
|
return;
|
|
}
|
|
|
|
void LabelDialog::OnChangeHfreq(wxGridEvent & WXUNUSED(event), int row, RowData *rd)
|
|
{
|
|
// Remember the value...no need to repopulate
|
|
double f;
|
|
mGrid->GetCellValue(row, Col_Hfreq).ToDouble(&f);
|
|
rd->selectedRegion.setF1(f, false);
|
|
mGrid->SetCellValue(row, Col_Lfreq, wxString::Format(wxT("%g"),
|
|
rd->selectedRegion.f0()));
|
|
|
|
return;
|
|
}
|
|
|
|
void LabelDialog::ReadSize(){
|
|
wxSize sz = GetSize();
|
|
int prefWidth, prefHeight;
|
|
gPrefs->Read(wxT("/LabelEditor/Width"), &prefWidth, sz.x);
|
|
gPrefs->Read(wxT("/LabelEditor/Height"), &prefHeight, sz.y);
|
|
|
|
wxRect screenRect(wxGetClientDisplayRect());
|
|
wxSize prefSize = wxSize(prefWidth, prefHeight);
|
|
prefSize.DecTo(screenRect.GetSize());
|
|
SetSize(prefSize);
|
|
}
|
|
|
|
void LabelDialog::WriteSize(){
|
|
wxSize sz = GetSize();
|
|
gPrefs->Write(wxT("/LabelEditor/Width"), sz.x);
|
|
gPrefs->Write(wxT("/LabelEditor/Height"), sz.y);
|
|
gPrefs->Flush();
|
|
}
|
|
|
|
void LabelDialog::OnOK(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
if (mGrid->IsCellEditControlShown()) {
|
|
mGrid->SaveEditControlValue();
|
|
mGrid->HideCellEditControl();
|
|
return;
|
|
}
|
|
|
|
// Standard handling
|
|
if (Validate() && TransferDataFromWindow()) {
|
|
WriteSize();
|
|
EndModal(wxID_OK);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void LabelDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
if (mGrid->IsCellEditControlShown()) {
|
|
auto editor = mGrid->GetCellEditor(mGrid->GetGridCursorRow(),
|
|
mGrid->GetGridCursorCol());
|
|
editor->Reset();
|
|
// To avoid memory leak, don't forget DecRef()!
|
|
editor->DecRef();
|
|
mGrid->HideCellEditControl();
|
|
return;
|
|
}
|
|
|
|
WriteSize();
|
|
// Standard handling
|
|
EndModal(wxID_CANCEL);
|
|
|
|
return;
|
|
}
|