/**********************************************************************

  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 <wx/defs.h>
#include <wx/listctrl.h>
#include <wx/dnd.h>

#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(XO("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(XO("Move rule &up"));
          MoveRuleDown = S.Id (EIPMoveRuleDown).AddButton(
             XO("Move rule &down"));
          MoveFilterUp = S.Id (EIPMoveFilterUp).AddButton(
             XO("Move f&ilter up"));
          MoveFilterDown = S.Id (EIPMoveFilterDown).AddButton(
             XO("Move &filter down"));
      }
      S.EndHorizontalLay();
      S.StartHorizontalLay (wxSHRINK, 0);
      {
          AddRule = S.Id (EIPAddRule).AddButton(XO("&Add new rule"));
          DelRule = S.Id (EIPDelRule).AddButton(XO("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" }
};
}
