/********************************************************************** Audacity: A Digital Audio Editor ExtImportPrefs.cpp LRN *******************************************************************//** \class ExtImportPrefs \brief A PrefsPanel used to select extended import filter options. *//*******************************************************************/ #include "../Audacity.h" #include "ExtImportPrefs.h" #include #include #include #include "../Prefs.h" #include "../ShuttleGui.h" #include "../import/Import.h" #include "../widgets/AudacityMessageBox.h" #include "../widgets/Grid.h" #define EXTIMPORT_MIME_SUPPORT 0 enum ExtImportPrefsControls { EIPPluginList = 20000, EIPRuleTable, EIPAddRule, EIPDelRule, EIPMoveRuleUp, EIPMoveRuleDown, EIPMoveFilterUp, EIPMoveFilterDown }; BEGIN_EVENT_TABLE(ExtImportPrefs, PrefsPanel) EVT_LIST_KEY_DOWN(EIPPluginList,ExtImportPrefs::OnPluginKeyDown) EVT_LIST_BEGIN_DRAG(EIPPluginList,ExtImportPrefs::OnPluginBeginDrag) EVT_KEY_DOWN (ExtImportPrefs::OnRuleTableKeyDown) EVT_GRID_CELL_LEFT_CLICK (ExtImportPrefs::OnRuleTableCellClick) EVT_GRID_EDITOR_HIDDEN (ExtImportPrefs::OnRuleTableEdit) EVT_GRID_SELECT_CELL (ExtImportPrefs::OnRuleTableSelect) EVT_GRID_RANGE_SELECT (ExtImportPrefs::OnRuleTableSelectRange) EVT_BUTTON(EIPAddRule,ExtImportPrefs::OnAddRule) EVT_BUTTON(EIPDelRule,ExtImportPrefs::OnDelRule) EVT_BUTTON(EIPMoveRuleUp,ExtImportPrefs::OnRuleMoveUp) EVT_BUTTON(EIPMoveRuleDown,ExtImportPrefs::OnRuleMoveDown) EVT_BUTTON(EIPMoveFilterUp,ExtImportPrefs::OnFilterMoveUp) EVT_BUTTON(EIPMoveFilterDown,ExtImportPrefs::OnFilterMoveDown) END_EVENT_TABLE() ExtImportPrefs::ExtImportPrefs(wxWindow * parent, wxWindowID winid) /* i18n-hint: Title of dialog governing "Extended", or "advanced," * audio file import options */ : PrefsPanel(parent, winid, XO("Extended Import")), RuleTable(NULL), PluginList(NULL), mCreateTable (false), mDragFocus (NULL), mFakeKeyEvent (false), mStopRecursiveSelection (false), last_selected (-1) { Populate(); // See bug #2315 for discussion // This should be reviewed and (possibly) removed after wx3.1.3. Bind(wxEVT_SHOW, &ExtImportPrefs::OnShow, this); } ExtImportPrefs::~ExtImportPrefs() { } ComponentInterfaceSymbol ExtImportPrefs::GetSymbol() { return EXT_IMPORT_PREFS_PLUGIN_SYMBOL; } TranslatableString ExtImportPrefs::GetDescription() { return XO("Preferences for ExtImport"); } wxString ExtImportPrefs::HelpPageName() { return "Extended_Import_Preferences"; } /// Creates the dialog and its contents. void ExtImportPrefs::Populate() { //------------------------- Main section -------------------- // Now construct the GUI itself. // Use 'eIsCreatingFromPrefs' so that the GUI is // initialised with values from gPrefs. ShuttleGui S(this, eIsCreatingFromPrefs); PopulateOrExchange(S); // ----------------------- End of main section -------------- } void ExtImportPrefs::PopulateOrExchange(ShuttleGui & S) { S.SetBorder(2); S.StartScroller(); S.TieCheckBox(XXO("A&ttempt to use filter in OpenFile dialog first"), {wxT("/ExtendedImport/OverrideExtendedImportByOpenFileDialogChoice"), true}); S.StartStatic(XO("Rules to choose import filters"), 1); { S.SetSizerProportion(1); S.StartHorizontalLay (wxEXPAND, 1); { bool fillRuleTable = false; if (RuleTable == NULL) { RuleTable = safenew Grid(S.GetParent(),EIPRuleTable); RuleTable->SetColLabelSize(RuleTable->GetDefaultRowSize()); #if EXTIMPORT_MIME_SUPPORT RuleTable->CreateGrid (0, 2, wxGrid::wxGridSelectRows); #else RuleTable->CreateGrid (0, 1, wxGrid::wxGridSelectRows); #endif RuleTable->DisableDragColMove (); RuleTable->DisableDragRowSize (); RuleTable->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTER); RuleTable->SetColLabelValue (0, _("File extensions")); #if EXTIMPORT_MIME_SUPPORT RuleTable->SetColLabelValue (1, _("Mime-types")); #endif RuleTable->SetRowLabelSize (0); RuleTable->SetSelectionMode (wxGrid::wxGridSelectRows); // call SetMinSize to enable scrolling on large content RuleTable->Fit(); RuleTable->SetMinSize(RuleTable->GetSize()); ExtImportPrefsDropTarget *dragtarget1 {}; RuleTable->SetDropTarget ( dragtarget1 = safenew ExtImportPrefsDropTarget( dragtext1 = safenew wxTextDataObject(wxT("")) ) ); dragtarget1->SetPrefs (this); RuleTable->EnableDragCell (true); fillRuleTable = true; } S.Position(wxEXPAND | wxALL) .AddWindow(RuleTable); PluginList = S.Id(EIPPluginList).AddListControl( { { XO("Importer order"), wxLIST_FORMAT_LEFT, wxLIST_AUTOSIZE_USEHEADER } }, wxLC_REPORT | wxLC_SINGLE_SEL ); if (fillRuleTable) { ExtImportPrefsDropTarget *dragtarget2 {}; PluginList->SetDropTarget ( dragtarget2 = safenew ExtImportPrefsDropTarget( dragtext2 = safenew wxTextDataObject(wxT("")) ) ); dragtarget2->SetPrefs (this); auto &items = Importer::Get().GetImportItems(); { int i = -1; for (const auto &item : items) AddItemToTable (++i, item.get()); } if (!items.empty()) { RuleTable->SelectRow(0); RuleTable->SetGridCursor(0,0); } } } S.EndHorizontalLay(); S.StartHorizontalLay (wxSHRINK, 0); { MoveRuleUp = S.Id (EIPMoveRuleUp).AddButton(XXO("Move rule &up")); MoveRuleDown = S.Id (EIPMoveRuleDown).AddButton( XXO("Move rule &down")); MoveFilterUp = S.Id (EIPMoveFilterUp).AddButton( XXO("Move f&ilter up")); MoveFilterDown = S.Id (EIPMoveFilterDown).AddButton( XXO("Move &filter down")); } S.EndHorizontalLay(); S.StartHorizontalLay (wxSHRINK, 0); { AddRule = S.Id (EIPAddRule).AddButton(XXO("&Add new rule")); DelRule = S.Id (EIPDelRule).AddButton(XXO("De&lete selected rule")); } S.EndHorizontalLay(); } S.EndStatic(); S.EndScroller(); Layout(); Fit(); SetMinSize(GetSize()); } bool ExtImportPrefs::Commit() { ShuttleGui S(this, eIsSavingToPrefs); PopulateOrExchange(S); return true; } // See bug #2315 for discussion. This should be reviewed // and (possibly) removed after wx3.1.3. void ExtImportPrefs::OnShow(wxShowEvent &event) { event.Skip(); if (event.IsShown()) { RuleTable->Refresh(); PluginList->Refresh(); } } void ExtImportPrefs::OnPluginKeyDown(wxListEvent& event) { for (int i = 0; i < 1; i++) { #ifdef __WXMAC__ if (!mFakeKeyEvent && !wxGetKeyState(WXK_COMMAND)) break; #else if (!mFakeKeyEvent && !wxGetKeyState(WXK_CONTROL)) break; #endif if (DoOnPluginKeyDown (event.GetKeyCode())) event.Skip(); } } void ExtImportPrefs::SwapPluginRows (int row1, int row2) { wxString t, t2; long d, d2; ImportPlugin *ip1, *ip2; auto &items = Importer::Get().GetImportItems(); ExtImportItem *item = NULL; if( last_selected >= 0 ) item = items[last_selected].get(); t = PluginList->GetItemText (row1); d = PluginList->GetItemData (row1); d2 = PluginList->GetItemData (row2); PluginList->SetItemText (row1, PluginList->GetItemText (row2)); PluginList->SetItemText (row2, t); if (d == -1 || d2 == -1) { PluginList->SetItemData (row1, PluginList->GetItemData (row2)); PluginList->SetItemData (row2, d); if( !item ) return; if (d == -1) { item->divider = row2; } else if (d2 == -1) { item->divider = row1; } } else { if( !item ) return; ip1 = item->filter_objects[d]; ip2 = item->filter_objects[d2]; item->filter_objects[d] = ip2; item->filter_objects[d2] = ip1; t = item->filters[d]; t2 = item->filters[d2]; item->filters[d] = t2; item->filters[d2] = t; } } bool ExtImportPrefs::DoOnPluginKeyDown (int code) { if (code != WXK_UP && code != WXK_DOWN) return false; long itemIndex = -1; long itemIndex2 = -1; itemIndex = PluginList->GetNextItem(itemIndex, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (itemIndex == -1) return false; if (last_selected == -1) return false; auto &items = Importer::Get().GetImportItems(); ExtImportItem *item = items[last_selected].get(); if (code == WXK_UP && itemIndex == 0) return false; else if (code == WXK_DOWN && itemIndex == PluginList->GetItemCount() - 1) return false; if (code == WXK_UP) { itemIndex2 = itemIndex - 1; } else if (code == WXK_DOWN) { itemIndex2 = itemIndex + 1; } SwapPluginRows (itemIndex, itemIndex2); if (mFakeKeyEvent) { PluginList->SetItemState (itemIndex, 0, wxLIST_STATE_SELECTED); PluginList->SetItemState (itemIndex2, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } int fcount = item->filter_objects.size(); if (item->divider >= fcount) { item->divider = -1; } if (item->divider < -1) item->divider = item->filter_objects.size() - 1; return true; } void ExtImportPrefs::SwapRows (int row1, int row2) { int t; wxString ts; if (row1 == row2) return; if (row1 > row2) { t = row1; row1 = row2; row2 = t; } auto &items = Importer::Get().GetImportItems(); auto &t1 = items[row1]; auto &t2 = items[row2]; std::swap(t1, t2); for (int i = 0; i < RuleTable->GetNumberCols(); i++) { ts = RuleTable->GetCellValue (row2, i); RuleTable->SetCellValue (row2, i, RuleTable->GetCellValue (row1, i)); RuleTable->SetCellValue (row1, i, ts); } } void ExtImportPrefs::OnPluginBeginDrag(wxListEvent& WXUNUSED(event)) { wxDropSource dragSource(this); dragtext2->SetText(wxT("")); dragSource.SetData(*dragtext2); mDragFocus = PluginList; if( mDragFocus == NULL ) return; wxDragResult result = dragSource.DoDragDrop(wxDrag_DefaultMove); mDragFocus = NULL; switch (result) { case wxDragCopy: case wxDragMove: case wxDragNone: return; break; default: break; } } void ExtImportPrefs::OnRuleTableKeyDown(wxKeyEvent& event) { int mods = event.GetModifiers(); if (mods & wxMOD_CMD && (event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_DOWN)) { DoOnRuleTableKeyDown (event.GetKeyCode()); } else { event.Skip(); } } void ExtImportPrefs::DoOnRuleTableKeyDown (int keycode) { int selrow = RuleTable->GetGridCursorRow (); wxString ts; if (keycode == WXK_UP) { if (selrow <= 0) return; SwapRows (selrow - 1, selrow); RuleTable->MoveCursorUp (false); RuleTable->SelectRow (selrow - 1); } else if (keycode == WXK_DOWN) { if (selrow == RuleTable->GetNumberRows() - 1) return; SwapRows (selrow, selrow + 1); RuleTable->MoveCursorDown (false); RuleTable->SelectRow (selrow + 1); } } void ExtImportPrefs::OnRuleTableSelect (wxGridEvent& event) { int toprow; event.Skip(); if (!event.Selecting() || mStopRecursiveSelection) return; toprow = event.GetRow(); if (toprow < 0) return; DoOnRuleTableSelect (toprow); } void ExtImportPrefs::OnRuleTableSelectRange (wxGridRangeSelectEvent& event) { int toprow; event.Skip(); if (!event.Selecting() || mStopRecursiveSelection) return; toprow = event.GetTopRow(); if (toprow < 0) return; DoOnRuleTableSelect (toprow); mStopRecursiveSelection = true; RuleTable->SelectRow (toprow); mStopRecursiveSelection = false; RuleTable->SetGridCursor (toprow, 0); } void ExtImportPrefs::DoOnRuleTableSelect (int toprow) { auto &items = Importer::Get().GetImportItems(); if (toprow < 0 || toprow > (int)items.size()) { return; } ExtImportItem *item = items[toprow].get(); PluginList->DeleteAllItems(); int fcount; fcount = item->filters.size(); int shift = 0; for (int i = 0; i < fcount; i++) { if (item->divider == i) { PluginList->InsertItem (i, _("Unused filters:")); PluginList->SetItemData (i, -1); shift = 1; } if (item->filter_objects[i] != NULL) { PluginList->InsertItem (i + shift, item->filter_objects[i]->GetPluginFormatDescription().Translation()); } else { PluginList->InsertItem (i + shift, item->filters[i]); } PluginList->SetItemData (i + shift, i); } if (item->divider == -1) { PluginList->InsertItem (fcount, _("Unused filters:")); PluginList->SetItemData (fcount, -1); } wxListItem info; info.SetId (0); info.SetColumn (0); info.SetStateMask (wxLIST_STATE_SELECTED); info.SetState (wxLIST_STATE_SELECTED); info.SetMask (wxLIST_MASK_STATE); PluginList->SetItem (info); PluginList->SetColumnWidth (0, wxLIST_AUTOSIZE); last_selected = toprow; } void ExtImportPrefs::OnRuleTableEdit (wxGridEvent& event) { int row = event.GetRow(); int col = event.GetCol(); auto &items = Importer::Get().GetImportItems(); ExtImportItem *item = items[row].get(); RuleTable->SaveEditControlValue(); wxString val = RuleTable->GetCellValue (row, col); int fixSpaces = wxNO; bool askedAboutSpaces = false; wxArrayString vals; wxString delims(wxT(":")); Importer::Get().StringToList (val, delims, vals); switch (col) { case 0: item->extensions.clear(); break; case 1: item->mime_types.clear(); break; } for (size_t i = 0; i < vals.size(); i++) { wxString trimmed = vals[i]; trimmed.Trim().Trim(false); if (trimmed != vals[i]) { if (!askedAboutSpaces) { fixSpaces = AudacityMessageBox( XO( "There are space characters (spaces, newlines, tabs or linefeeds) in one of \ the items. They are likely to break the pattern matching. Unless you know \ what you are doing, it is recommended to trim spaces. Do you want \ Audacity to trim spaces for you?"), XO("Spaces detected"), wxYES_NO); askedAboutSpaces = true; } if (fixSpaces != wxYES) { trimmed = vals[i]; } else { vals[i] = trimmed; } } switch (col) { case 0: item->extensions.push_back(trimmed); break; case 1: item->mime_types.push_back(trimmed); break; } } if (fixSpaces == wxYES) { wxString vals_as_string; for (size_t i = 0; i < vals.size(); i++) { if (i > 0) vals_as_string.Append (wxT(":")); vals_as_string.Append (vals[i]); } RuleTable->SetCellValue (row, col, vals_as_string); } RuleTable->AutoSizeColumns (); } void ExtImportPrefs::AddItemToTable (int index, const ExtImportItem *item) { wxString extensions, mime_types; if (item->extensions.size() > 0) { extensions.Append (item->extensions[0]); for (unsigned int i = 1; i < item->extensions.size(); i++) { extensions.Append (wxT(":")); extensions.Append (item->extensions[i]); } } if (item->mime_types.size() > 0) { mime_types.Append (item->mime_types[0]); for (unsigned int i = 1; i < item->mime_types.size(); i++) { mime_types.Append (wxT(":")); mime_types.Append (item->mime_types[i]); } } RuleTable->InsertRows (index, 1); RuleTable->SetCellValue (index, 0, extensions); #if EXTIMPORT_MIME_SUPPORT RuleTable->SetCellValue (index, 1, mime_types); #endif RuleTable->AutoSizeColumns (); } void ExtImportPrefs::OnAddRule(wxCommandEvent& WXUNUSED(event)) { auto &items = Importer::Get().GetImportItems(); auto uitem = Importer::Get().CreateDefaultImportItem(); auto item = uitem.get(); items.push_back(std::move(uitem)); AddItemToTable (RuleTable->GetNumberRows (), item); RuleTable->SelectRow(RuleTable->GetNumberRows () - 1); RuleTable->SetGridCursor (RuleTable->GetNumberRows () - 1, 0); RuleTable->SetFocus(); } void ExtImportPrefs::OnDelRule(wxCommandEvent& WXUNUSED(event)) { if (last_selected < 0) return; auto &items = Importer::Get().GetImportItems(); int msgres = AudacityMessageBox ( XO("Do you really want to delete selected rule?"), XO("Rule deletion confirmation"), wxYES_NO, RuleTable); // Yes or no, there is no third! if (msgres != wxYES) return; RuleTable->DeleteRows (last_selected); items.erase (items.begin() + last_selected); RuleTable->AutoSizeColumns (); if (last_selected >= RuleTable->GetNumberRows ()) last_selected = RuleTable->GetNumberRows () - 1; if (last_selected >= 0) { RuleTable->SelectRow(last_selected); RuleTable->SetGridCursor (last_selected, 0); } } void ExtImportPrefs::OnRuleMoveUp(wxCommandEvent& WXUNUSED(event)) { DoOnRuleTableKeyDown (WXK_UP); } void ExtImportPrefs::OnRuleMoveDown(wxCommandEvent& WXUNUSED(event)) { DoOnRuleTableKeyDown (WXK_DOWN); } void ExtImportPrefs::FakeOnPluginKeyDown (int keycode) { wxListEvent fakeevent(wxEVT_COMMAND_LIST_KEY_DOWN, EIPPluginList); fakeevent.SetEventObject(this); fakeevent.m_code = keycode; mFakeKeyEvent = true; GetEventHandler()->ProcessEvent (fakeevent); mFakeKeyEvent = false; } void ExtImportPrefs::OnFilterMoveUp(wxCommandEvent& WXUNUSED(event)) { FakeOnPluginKeyDown (WXK_UP); } void ExtImportPrefs::OnFilterMoveDown(wxCommandEvent& WXUNUSED(event)) { FakeOnPluginKeyDown (WXK_DOWN); } void ExtImportPrefs::OnRuleTableCellClick (wxGridEvent& event) { int row = event.GetRow(); RuleTable->SelectRow (row, false); RuleTable->SetGridCursor (row, 0); wxDropSource dragSource(this); dragtext1->SetText(wxT("")); dragSource.SetData(*dragtext1); mDragFocus = RuleTable; wxDragResult result = dragSource.DoDragDrop(wxDrag_DefaultMove); mDragFocus = NULL; switch (result) { case wxDragCopy: /* copy the data */ case wxDragMove: case wxDragNone: return; break; default: /* do nothing */ break; } event.Skip(); } ExtImportPrefsDropTarget::ExtImportPrefsDropTarget(wxDataObject *dataObject) : wxDropTarget(dataObject) { mPrefs = NULL; } ExtImportPrefsDropTarget::~ExtImportPrefsDropTarget () { } void ExtImportPrefsDropTarget::SetPrefs (ExtImportPrefs *prefs) { mPrefs = prefs; } wxDragResult ExtImportPrefsDropTarget::OnData(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y), wxDragResult def) { return def; } #if defined(__WXMSW__) /* wxListCtrl::FindItem() in wxPoint()-taking mode works only for lists in * Small/Large-icon mode * wxListCtrl::HitTest() on Windows only hits item label rather than its whole * row, which makes it difficult to drag over items with short or non-existent * labels. */ long wxCustomFindItem(wxListCtrl *list, int x, int y) { long count = list->GetItemCount(); wxRect r; for (long i = 0; i < count; i++) { if (list->GetItemRect (i, r)) { if (r.Contains (x, y)) return i; } } return -1; } #endif bool ExtImportPrefsDropTarget::OnDrop(wxCoord x, wxCoord y) { if (mPrefs == NULL) return false; wxListCtrl *PluginList = mPrefs->GetPluginList(); Grid *RuleTable = mPrefs->GetRuleTable(); if (mPrefs->GetDragFocus() == RuleTable) { if (RuleTable->YToRow( RuleTable->CalcUnscrolledPosition(wxPoint(x, y)).y) == wxNOT_FOUND) return false; } else if (mPrefs->GetDragFocus() == PluginList) { #if defined(__WXMSW__) long item = wxCustomFindItem (PluginList, x, y); #else int flags = 0; long item = PluginList->HitTest (wxPoint (x, y), flags, NULL); #endif if (item < 0) return false; } return true; } wxDragResult ExtImportPrefsDropTarget::OnEnter(wxCoord x, wxCoord y, wxDragResult def) { return OnDragOver(x, y, def); } wxDragResult ExtImportPrefsDropTarget::OnDragOver(wxCoord x, wxCoord y, wxDragResult WXUNUSED(def)) { if (mPrefs == NULL) return wxDragNone; wxListCtrl *PluginList = mPrefs->GetPluginList(); Grid *RuleTable = mPrefs->GetRuleTable(); if (mPrefs->GetDragFocus() == RuleTable) { int row; row = RuleTable->YToRow(RuleTable->CalcUnscrolledPosition(wxPoint(x, y)).y); if (row == wxNOT_FOUND) return wxDragNone; int cRow = RuleTable->GetGridCursorRow (); if (row != cRow) { mPrefs->SwapRows (cRow, row); RuleTable->SetGridCursor (row, 0); RuleTable->SelectRow (row); } } else if (mPrefs->GetDragFocus() == PluginList) { #if defined(__WXMSW__) long item = wxCustomFindItem (PluginList, x, y); #else int flags = 0; long item = PluginList->HitTest (wxPoint (x, y), flags, NULL); #endif if (item < 0) return wxDragNone; long selected = -1; selected = PluginList->GetNextItem(selected, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (selected == -1) return wxDragNone; if (item != selected) { mPrefs->SwapPluginRows(selected, item); PluginList->SetItemState (selected, 0, wxLIST_STATE_SELECTED); PluginList->SetItemState (item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } } return wxDragMove; } void ExtImportPrefsDropTarget::OnLeave() { } namespace{ PrefsPanel::Registration sAttachment{ "ExtImport", [](wxWindow *parent, wxWindowID winid, AudacityProject *) { wxASSERT(parent); // to justify safenew return safenew ExtImportPrefs(parent, winid); }, false, // Place as a lower level of the tree of pages: { "ImportExport" } }; }