1
0
mirror of https://github.com/cookiengineer/audacity synced 2026-03-05 14:11:00 +01:00
Files
audacity/src/LabelDialog.cpp
lllucius bdcefb4850 Preliminary changes for wxWidgets 3.0.1
We can't go to 3.0.1 yet as there are still build issues on
Linux and OSX.  You can get Windows to build, but there's
still some display issues.

These changes should work with wxWidgets 2.8.12 as well, so
we can take our time to get things working properly before
switching over.
2014-10-06 08:10:50 +00:00

795 lines
20 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
LabelDialog.cpp
Dominic Mazzoni
*******************************************************************//**
\class LabelDialog
\brief Dialog for editing labels.
*//*******************************************************************/
#include "Audacity.h"
#include <wx/button.h>
#include <wx/defs.h>
#include <wx/choice.h>
#include <wx/dc.h>
#include <wx/dialog.h>
#include <wx/filedlg.h>
#include <wx/grid.h>
#include <wx/intl.h>
#include <wx/msgdlg.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textdlg.h>
#include "Internat.h"
#include "LabelDialog.h"
#include "LabelTrack.h"
#include "Prefs.h"
#include "Project.h"
#include "Track.h"
#include "ViewInfo.h"
#include "widgets/TimeTextCtrl.h"
#include "FileDialog.h"
enum Column
{
Col_Track,
Col_Label,
Col_Stime,
Col_Etime,
Col_Max
};
class RowData
{
public:
RowData() {};
int index;
wxString title;
SelectedRegion selectedRegion;
};
enum {
ID_INSERTA = 11000,
ID_INSERTB,
ID_REMOVE,
ID_IMPORT,
ID_EXPORT
};
BEGIN_EVENT_TABLE(LabelDialog, wxDialog)
EVT_GRID_SELECT_CELL(LabelDialog::OnSelectCell)
EVT_GRID_CELL_CHANGE(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)
END_EVENT_TABLE()
LabelDialog::LabelDialog(wxWindow *parent,
DirManager *dirmanager,
TrackList *tracks,
ViewInfo &viewinfo,
double rate,
const wxString & format)
: wxDialog(parent,
wxID_ANY,
_("Edit Labels"),
wxDefaultPosition,
wxSize(800, 600),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
mDirManager(dirmanager),
mTracks(tracks),
mViewInfo(&viewinfo),
mRate(rate),
mFormat(format)
{
// Create the main sizer
wxBoxSizer *vs = new wxBoxSizer(wxVERTICAL);
// A little instruction
wxStaticText *instruct =
new wxStaticText(this,
wxID_ANY,
_("Press F2 or double click to edit cell contents."));
instruct->SetName(instruct->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
vs->Add(instruct,
0,
wxALIGN_LEFT | wxALL,
5);
// Create the main sizer
mGrid = new Grid(this, wxID_ANY);
vs->Add(mGrid, 1, wxEXPAND | wxALL, 5);
// Create the action buttons
wxBoxSizer *hs = new wxBoxSizer(wxHORIZONTAL);
hs->Add(new wxButton(this, ID_INSERTA, _("Insert &After")), 1, wxCENTER | wxALL, 5);
hs->Add(new wxButton(this, ID_INSERTB, _("Insert &Before")), 1, wxCENTER | wxALL, 5);
hs->Add(new wxButton(this, ID_REMOVE, _("&Remove")), 1, wxCENTER | wxALL, 5);
hs->Add(new wxButton(this, ID_IMPORT, _("&Import...")), 1, wxCENTER | wxALL, 5);
hs->Add(new wxButton(this, ID_EXPORT, _("&Export...")), 1, wxCENTER | wxALL, 5);
vs->Add(hs, 0, wxEXPAND | wxCENTER | wxALL, 5);
// Create the exit buttons
vs->Add(CreateStdButtonSizer(this, eCancelButton|eOkButton), 0, wxEXPAND);
// Make it so
SetSizer(vs);
// Build the initial (empty) grid
mGrid->CreateGrid(0, Col_Max);
mGrid->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTER);
/* i18n-hint: (noun). A track contains waves, audio etc.*/
mGrid->SetColLabelValue(0,_("Track"));
/* i18n-hint: (noun)*/
mGrid->SetColLabelValue(1,_("Label"));
/* i18n-hint: (noun) of a label*/
mGrid->SetColLabelValue(2,_("Start Time"));
/* i18n-hint: (noun) of a label*/
mGrid->SetColLabelValue(3,_("End Time"));
// Create and remember editors. No need to delete these as the wxGrid will
// do it for us.
mChoiceEditor = (ChoiceEditor *) mGrid->GetDefaultEditorForType(GRID_VALUE_CHOICE);
mTimeEditor = (TimeEditor *) mGrid->GetDefaultEditorForType(GRID_VALUE_TIME);
// Initialize and set the track name column attributes
wxGridCellAttr *attr = new wxGridCellAttr();
attr->SetEditor(mChoiceEditor);
mGrid->SetColAttr(Col_Track, attr);
mTrackNames.Add(_("New..."));
// Initialize and set the time column attributes
attr = new wxGridCellAttr();
attr->SetRenderer(mGrid->GetDefaultRendererForType(GRID_VALUE_TIME));
attr->SetEditor(mTimeEditor);
attr->SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
mGrid->SetColAttr(Col_Stime, attr);
mGrid->SetColAttr(Col_Etime, 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));
// Layout the works
Layout();
// 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;
SetClientSize(r.width, 300);
// Make sure it doesn't go below this size
r = GetRect();
SetSizeHints(r.GetWidth(), r.GetHeight());
// Center on display
Center();
}
LabelDialog::~LabelDialog()
{
int cnt = mData.GetCount();
// Delete any RowData we've allocated
while (cnt) {
RowData *rd = mData[--cnt];
delete rd;
}
}
bool LabelDialog::TransferDataToWindow()
{
int cnt = mData.GetCount();
int i;
// Set the editor parameters. Do this each time since they may change
// due to new tracks and change in TimeTextCtrl format. Rate won't
// change but might as well leave it here.
mChoiceEditor->SetChoices(mTrackNames);
mTimeEditor->SetFormat(mFormat);
mTimeEditor->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()));
// PRL: to do: -- populate future additional selection fields
// and write event code to update them from controls
}
// 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);
// We're done, so allow the grid to redraw
mGrid->EndBatch();
return true;
}
bool LabelDialog::Show(bool show)
{
bool ret = wxDialog::Show(show);
// Set initial row
// (This will not work until the grid is actually displayed)
if (mInitialRow != -1) {
mGrid->SetGridCursor(mInitialRow, Col_Label);
}
return ret;
}
bool LabelDialog::TransferDataFromWindow()
{
int cnt = mData.GetCount();
int i;
TrackListIterator iter(mTracks);
Track *t;
int tndx = 0;
// Clear all label tracks of labels
for (t = iter.First(); t; t = iter.Next()) {
if (t->GetKind() == Track::Label) {
LabelTrack *lt = (LabelTrack *)t;
tndx++;
for (i = lt->GetNumLabels() - 1; i >= 0 ; i--) {
lt->DeleteLabel(i);
}
}
}
// Create any added tracks
while (tndx < (int)mTrackNames.GetCount() - 1) {
// Extract the name
wxString name = mTrackNames[tndx + 1].AfterFirst(wxT('-')).Mid(1);
// Create the new track and add to track list
LabelTrack *newTrack = new LabelTrack(mDirManager);
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;
for (t = iter.First(); t; t = iter.Next()) {
if (t->GetKind() == Track::Label && rd->index == tndx++) {
break;
}
}
wxASSERT(t);
if (!t)
return false;
// Add the label to it
((LabelTrack *) t)->AddLabel(rd->selectedRegion, rd->title);
((LabelTrack *) t)->Unselect();
}
return true;
}
bool LabelDialog::Validate()
{
if (mGrid->IsCellEditControlShown()) {
mGrid->HideCellEditControl();
mGrid->SaveEditControlValue();
}
return true;
}
wxString LabelDialog::TrackName(int & index, wxString dflt)
{
// Generate a new track name if the passed index is out of range
if (index < 1 || index >= (int)mTrackNames.GetCount()) {
index = mTrackNames.GetCount();
mTrackNames.Add(wxString::Format(wxT("%d - %s"), index, dflt.c_str()));
}
// Return the track name
return mTrackNames[index];
}
void LabelDialog::FindAllLabels()
{
TrackListIterator iter(mTracks);
Track *t;
mInitialRow = -1;
// Add labels from all label tracks
for (t = iter.First(); t; t = iter.Next()) {
if (t->GetKind() == Track::Label) {
AddLabels((LabelTrack *) t);
}
}
if (mData.GetCount() == 0) {
wxCommandEvent e;
OnInsert(e);
}
}
void LabelDialog::AddLabels(LabelTrack *t)
{
wxString lab;
int tndx = 0;
int i;
// Add a new track name
TrackName(tndx, t->GetName());
// Add each label in the track
for (i = 0; i < t->GetNumLabels(); i++) {
const LabelStruct *ls = t->GetLabel(i);
RowData *rd = new RowData();
rd->index = tndx;
rd->selectedRegion = ls->selectedRegion;
rd->title = ls->title;
mData.Add(rd);
if (i == t->getSelectedIndex()) {
mInitialRow = mData.GetCount() - 1;
}
}
}
void LabelDialog::OnUpdate(wxCommandEvent &event)
{
// Remember the new format and repopulate grid
mFormat = event.GetString();
TransferDataToWindow();
event.Skip(false);
}
void LabelDialog::OnInsert(wxCommandEvent &event)
{
RowData *rd = new RowData();
int cnt = mData.GetCount();
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->GetCursorRow();
if (row > 0 && row >= cnt) {
index = mTrackNames.Index(mGrid->GetCellValue(row - 1, Col_Track));
}
else {
index = mTrackNames.Index(mGrid->GetCellValue(row, Col_Track));
}
}
// Initialize the new label
rd->index = index;
rd->selectedRegion = SelectedRegion();
rd->title = wxT("");
// Insert it before or after the current row
if (event.GetId() == ID_INSERTA && row < cnt) {
row++;
}
mData.Insert(rd, row);
// 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->GetCursorRow();
int col = mGrid->GetCursorColumn();
int cnt = mData.GetCount();
// 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];
mTrackNames.RemoveAt(rd->index);
mData.RemoveAt(row);
delete rd;
// 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.GetCount() == 0 && wxWindow::FindFocus() == mGrid->GetGridWindow()) {
wxWindow *ok = wxWindow::FindWindowById( wxID_OK, this);
if (ok) {
ok->SetFocus();
}
}
}
void LabelDialog::OnImport(wxCommandEvent & WXUNUSED(event))
{
wxString path = gPrefs->Read(wxT("/DefaultOpenPath"),::wxGetCwd());
// Ask user for a filename
wxString fileName =
FileSelector(_("Select a text file containing labels..."),
path, // Path
wxT(""), // Name
wxT(".txt"), // Extension
_("Text files (*.txt)|*.txt|All files|*"),
wxRESIZE_BORDER, // Flags
this); // Parent
// They gave us one...
if (fileName != wxT("")) {
path =::wxPathOnly(fileName);
gPrefs->Write(wxT("/DefaultOpenPath"), path);
gPrefs->Flush();
wxTextFile f;
// Get at the data
f.Open(fileName);
if (!f.IsOpened()) {
wxMessageBox(_("Could not open file: ") + fileName);
}
else {
// Create a temporary label track and load the labels
// into it
LabelTrack *lt = new LabelTrack(mDirManager);
lt->Import(f);
// Add the labesls to our collection
AddLabels(lt);
// Done with the temporary track
delete lt;
}
// Repopulate the grid
TransferDataToWindow();
}
}
void LabelDialog::OnExport(wxCommandEvent & WXUNUSED(event))
{
int cnt = mData.GetCount();
// Silly user (could just disable the button, but that's a hassle ;-))
if (cnt == 0) {
wxMessageBox(_("No labels to export."));
return;
}
// Extract the actual name.
wxString fName = mTrackNames[mTrackNames.GetCount() - 1].AfterFirst(wxT('-')).Mid(1);
fName = FileSelector(_("Export Labels As:"),
wxEmptyString,
fName.c_str(),
wxT("txt"),
wxT("*.txt"),
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
this);
if (fName == wxT(""))
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 *temp = new wxFile();
temp->Create(fName);
delete temp;
#else
f.Create();
#endif
f.Open();
if (!f.IsOpened()) {
wxMessageBox(_("Couldn't write to file: ") + fName);
return;
}
// Transfer our collection to a temporary label track
LabelTrack *lt = new LabelTrack(mDirManager);
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);
delete lt;
#ifdef __WXMAC__
f.Write(wxTextFileType_Mac);
#else
f.Write();
#endif
f.Close();
}
void LabelDialog::OnSelectCell(wxGridEvent &event)
{
TrackListIterator iter(mTracks);
Track *t = iter.First();
while( t )
{
t->SetSelected( true );
t = iter.Next();
}
if (!mData.empty())
{
RowData *rd;
rd = mData[event.GetRow()];
mViewInfo->selectedRegion = rd->selectedRegion;
GetActiveProject()->RedrawProject();
}
event.Skip();
}
void LabelDialog::OnCellChange(wxGridEvent &event)
{
static bool guard = false;
int row = event.GetRow();
RowData *rd;
// 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.
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;
}
// 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 (mTrackNames.Index(val) == 0) {
wxTextEntryDialog d(this,
_("New Label Track"),
_("Enter track name"),
/* i18n-hint: (noun) it's the name of a kind of track.*/
_("Label Track"));
// User canceled so repopulating the grid will set the track
// name to the orignal 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 = 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::OnOK(wxCommandEvent & WXUNUSED(event))
{
if (mGrid->IsCellEditControlShown()) {
mGrid->SaveEditControlValue();
mGrid->HideCellEditControl();
return;
}
// Standard handling
if (Validate() && TransferDataFromWindow()) {
EndModal(wxID_OK);
}
return;
}
void LabelDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
{
if (mGrid->IsCellEditControlShown()) {
mGrid->GetCellEditor(mGrid->GetGridCursorRow(),
mGrid->GetGridCursorCol())
->Reset();
mGrid->HideCellEditControl();
return;
}
// Standard handling
EndModal(wxID_CANCEL);
return;
}