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

  Audacity: A Digital Audio Editor

  DeviceToolBar.cpp

  Dominic Mazzoni

*******************************************************************//*!

\class DeviceToolBar
\brief A toobar to allow easier changing of input and output devices .

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



#include "DeviceToolBar.h"
#include "ToolManager.h"

#include <thread>

// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>

#include <wx/setup.h> // for wxUSE_* macros

#ifndef WX_PRECOMP
#include <wx/app.h>
#include <wx/choice.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/statbmp.h>
#include <wx/stattext.h>
#include <wx/tooltip.h>
#endif

#include "../TrackPanel.h"

#include "AColor.h"
#include "AllThemeResources.h"
#include "AudioIOBase.h"
#include "ImageManipulation.h"
#include "../KeyboardCapture.h"
#include "Prefs.h"
#include "Project.h"
#include "ShuttleGui.h"
#include "../widgets/Grabber.h"
#include "DeviceManager.h"
#include "AudacityMessageBox.h"

#if wxUSE_ACCESSIBILITY
#include "WindowAccessible.h"
#endif

IMPLEMENT_CLASS(DeviceToolBar, ToolBar);

////////////////////////////////////////////////////////////
/// Methods for DeviceToolBar
////////////////////////////////////////////////////////////

BEGIN_EVENT_TABLE(DeviceToolBar, ToolBar)
   EVT_CHOICE(wxID_ANY, DeviceToolBar::OnChoice)
   EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, DeviceToolBar::OnCaptureKey)
END_EVENT_TABLE()

int DeviceToolbarPrefsID()
{
   static int value = wxNewId();
   return value;
}

Identifier DeviceToolBar::ID()
{
   return wxT("Device");
}

//Standard constructor
DeviceToolBar::DeviceToolBar( AudacityProject &project )
: ToolBar( project, XO("Device"), ID(), true )
{
   mSubscription = DeviceManager::Instance()->Subscribe(
      *this, &DeviceToolBar::OnRescannedDevices );
}

DeviceToolBar::~DeviceToolBar()
{
}

bool DeviceToolBar::ShownByDefault() const
{
   return false;
}

DeviceToolBar &DeviceToolBar::Get( AudacityProject &project )
{
   auto &toolManager = ToolManager::Get( project );
   return *static_cast<DeviceToolBar*>(toolManager.GetToolBar(ID()));
}

const DeviceToolBar &DeviceToolBar::Get( const AudacityProject &project )
{
   return Get( const_cast<AudacityProject&>( project )) ;
}

void DeviceToolBar::Create(wxWindow *parent)
{
   ToolBar::Create(parent);

   // Simulate a size event to set initial meter placement/size
   wxSizeEvent event(GetSize(), GetId());
   event.SetEventObject(this);
   GetEventHandler()->ProcessEvent(event);
}

void DeviceToolBar::DeinitChildren()
{
   mInput         = NULL;
   mOutput        = NULL;
   mInputChannels = NULL;
   mHost          = NULL;
}

void DeviceToolBar::Populate()
{
   SetBackgroundColour( theTheme.Colour( clrMedium  ) );
   DeinitChildren();

   // Hosts
   mHost = safenew wxChoice(this,
                            wxID_ANY,
                            wxDefaultPosition,
                            wxDefaultSize);
#if wxUSE_ACCESSIBILITY
   // so that name can be set on a standard control
   mHost->SetAccessible(safenew WindowAccessible(mHost));
#endif
   Add(mHost, 15, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 1);

   // Input device
   Add(safenew AStaticBitmap(this,
                             wxID_ANY,
                             theTheme.Bitmap(bmpMic)), 0, wxALIGN_CENTER_VERTICAL);
   mInput = safenew wxChoice(this,
                             wxID_ANY,
                             wxDefaultPosition,
                             wxDefaultSize);
#if wxUSE_ACCESSIBILITY
   // so that name can be set on a standard control
   mInput->SetAccessible(safenew WindowAccessible(mInput));
#endif
   Add(mInput, 30, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 1);

   // Input channels
   mInputChannels = safenew wxChoice(this,
                                     wxID_ANY,
                                     wxDefaultPosition,
                                     wxDefaultSize);
#if wxUSE_ACCESSIBILITY
   // so that name can be set on a standard control
   mInputChannels->SetAccessible(safenew WindowAccessible(mInputChannels));
#endif
   Add(mInputChannels, 20, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 1);

   // Output device
   Add(safenew AStaticBitmap(this,
                             wxID_ANY,
                             theTheme.Bitmap(bmpSpeaker)), 0, wxALIGN_CENTER_VERTICAL);
   mOutput = safenew wxChoice(this,
                              wxID_ANY,
                              wxDefaultPosition,
                              wxDefaultSize);
#if wxUSE_ACCESSIBILITY
   // so that name can be set on a standard control
   mOutput->SetAccessible(safenew WindowAccessible(mOutput));
#endif
   Add(mOutput, 30, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 1);

#if defined(__WXGTK3__)
   // Nothing special
#elif defined(__WXGTK__)
   // Scale the font to fit inside (hopefully)
   wxFont font = mHost->GetFont();
   font.Scale((double) toolbarSingle / mHost->GetSize().GetHeight());

   // Set it
   mHost->SetFont(font);
   mInput->SetFont(font);
   mInputChannels->SetFont(font);
   mOutput->SetFont(font);
#endif

   mHost->Bind(wxEVT_SET_FOCUS,
               &DeviceToolBar::OnFocus,
               this);
   mHost->Bind(wxEVT_KILL_FOCUS,
               &DeviceToolBar::OnFocus,
               this);
   mOutput->Bind(wxEVT_SET_FOCUS,
                 &DeviceToolBar::OnFocus,
                 this);
   mOutput->Bind(wxEVT_KILL_FOCUS,
                 &DeviceToolBar::OnFocus,
                 this);
   mInput->Bind(wxEVT_SET_FOCUS,
                 &DeviceToolBar::OnFocus,
                 this);
   mInput->Bind(wxEVT_KILL_FOCUS,
                 &DeviceToolBar::OnFocus,
                 this);
   mInputChannels->Bind(wxEVT_SET_FOCUS,
                 &DeviceToolBar::OnFocus,
                 this);
   mInputChannels->Bind(wxEVT_KILL_FOCUS,
                 &DeviceToolBar::OnFocus,
                 this);

   SetNames();

   RefillCombos();
}

void DeviceToolBar::OnFocus(wxFocusEvent &event)
{
   KeyboardCapture::OnFocus( *this, event );
}

void DeviceToolBar::OnCaptureKey(wxCommandEvent &event)
{
   wxKeyEvent *kevent = (wxKeyEvent *)event.GetEventObject();
   int keyCode = kevent->GetKeyCode();

   // Pass UP/DOWN/LEFT/RIGHT through for input/output choice
   if (FindFocus() == mInput && (keyCode == WXK_LEFT || keyCode == WXK_RIGHT
                                 || keyCode == WXK_UP || keyCode == WXK_DOWN)) {
      return;
   }

   if (FindFocus() == mOutput && (keyCode == WXK_LEFT || keyCode == WXK_RIGHT
                                 || keyCode == WXK_UP || keyCode == WXK_DOWN)) {
      return;
   }
   event.Skip();

   return;
}

void DeviceToolBar::UpdatePrefs()
{
   wxString desc;
   const std::vector<DeviceSourceMap> &inMaps  = DeviceManager::Instance()->GetInputDeviceMaps();
   const std::vector<DeviceSourceMap> &outMaps = DeviceManager::Instance()->GetOutputDeviceMaps();


   int hostSelectionIndex = mHost->GetSelection();
   wxString oldHost = hostSelectionIndex >= 0 ? mHost->GetString(hostSelectionIndex) :
                                                wxString{};
   auto hostName = AudioIOHost.Read();

   // if the prefs host name doesn't match the one displayed, it changed
   // in another project's DeviceToolBar, so we need to repopulate everything.
   if (oldHost != hostName)
      FillHostDevices();

   auto devName = AudioIORecordingDevice.Read();
   auto sourceName = AudioIORecordingSource.Read();
   if (sourceName.empty())
      desc = devName;
   else
      desc = devName + wxT(": ") + sourceName;

   if (mInput->GetStringSelection() != desc &&
       mInput->FindString(desc) != wxNOT_FOUND) {
      mInput->SetStringSelection(desc);
      FillInputChannels();
   } else if (mInput->GetStringSelection() != desc && mInput->GetCount()) {
      for (size_t i = 0; i < inMaps.size(); i++) {
         if (inMaps[i].hostString == hostName &&
             MakeDeviceSourceString(&inMaps[i]) == mInput->GetString(0)) {
            // use the default.  It should exist but check just in case, falling back on the 0 index.
            DeviceSourceMap *defaultMap = DeviceManager::Instance()->GetDefaultInputDevice(inMaps[i].hostIndex);
            if (defaultMap) {
               mInput->SetStringSelection(MakeDeviceSourceString(defaultMap));
               SetDevices(defaultMap, NULL);
            } else {
               //use the first item (0th index) if we have no familiar devices
               mInput->SetSelection(0);
               SetDevices(&inMaps[i], NULL);
            }
            break;
         }
      }
   }

   devName = AudioIOPlaybackDevice.Read();
   sourceName = AudioIOPlaybackSource.Read();
   if (sourceName.empty())
      desc = devName;
   else
      desc = devName + wxT(": ") + sourceName;

   if (mOutput->GetStringSelection() != desc &&
       mOutput->FindString(desc) != wxNOT_FOUND) {
      mOutput->SetStringSelection(desc);
   } else if (mOutput->GetStringSelection() != desc &&
              mOutput->GetCount()) {
      for (size_t i = 0; i < outMaps.size(); i++) {
         if (outMaps[i].hostString == hostName &&
             MakeDeviceSourceString(&outMaps[i]) == mOutput->GetString(0)) {
            // use the default.  It should exist but check just in case, falling back on the 0 index.
            DeviceSourceMap *defaultMap = DeviceManager::Instance()->GetDefaultOutputDevice(outMaps[i].hostIndex);
            if (defaultMap) {
               mOutput->SetStringSelection(MakeDeviceSourceString(defaultMap));
               SetDevices(NULL, defaultMap);
            } else {
               //use the first item (0th index) if we have no familiar devices
               mOutput->SetSelection(0);
               SetDevices(NULL, &outMaps[i]);
            }
            break;
         }
      }
   }

   long oldChannels;
   oldChannels = mInputChannels->GetSelection() + 1;
   auto newChannels = AudioIORecordChannels.ReadWithDefault(0);
   if (newChannels > 0 && oldChannels != newChannels)
      mInputChannels->SetSelection(newChannels - 1);

   if (!hostName.empty() && mHost->GetStringSelection() != hostName)
      mHost->SetStringSelection(hostName);

   RegenerateTooltips();

   // Set label to pull in language change
   SetLabel(XO("Device"));

   // Give base class a chance
   ToolBar::UpdatePrefs();

   Layout();
   Refresh();
}

void DeviceToolBar::UpdateSelectedPrefs( int id )
{
   if (id == DeviceToolbarPrefsID())
      UpdatePrefs();
   ToolBar::UpdateSelectedPrefs( id );
}


void DeviceToolBar::EnableDisableButtons()
{
   auto gAudioIO = AudioIOBase::Get();
   if (gAudioIO) {
      // we allow changes when monitoring, but not when recording
      bool audioStreamActive = gAudioIO->IsStreamActive() && !gAudioIO->IsMonitoring();

      // Here we should relinquish focus
      if (audioStreamActive) {
         wxWindow *focus = wxWindow::FindFocus();
         if (focus == mHost || focus == mInput || focus == mOutput || focus == mInputChannels)
            TrackPanel::Get( mProject ).SetFocus();
      }

      mHost->Enable(!audioStreamActive);
      mInput->Enable(!audioStreamActive);
      mOutput->Enable(!audioStreamActive);
      mInputChannels->Enable(!audioStreamActive);
   }
}

void DeviceToolBar::SetNames()
{
   /* i18n-hint: (noun) It's the device used for playback.*/
   mOutput->SetName(_("Playback Device"));
   /* i18n-hint: (noun) It's the device used for recording.*/
   mInput->SetName(_("Recording Device"));
   mHost->SetName(_("Audio Host"));
   mInputChannels->SetName(_("Recording Channels"));
}

void DeviceToolBar::RegenerateTooltips()
{
#if wxUSE_TOOLTIPS
   SetNames();
   mOutput->SetToolTip(mOutput->GetName() + wxT(" - ") + mOutput->GetStringSelection());
   mInput->SetToolTip(mInput->GetName() + wxT(" - ") + mInput->GetStringSelection());
   mHost->SetToolTip(mHost->GetName() + wxT(" - ") + mHost->GetStringSelection());
   mInputChannels->SetToolTip(mInputChannels->GetName() + wxT(" - ") + mInputChannels->GetStringSelection());
#endif
}

void DeviceToolBar::RefillCombos()
{
   FillHosts();
   FillHostDevices();
   FillInputChannels();
   // make the device display selection reflect the prefs if they exist
   UpdatePrefs();
}

void DeviceToolBar::FillHosts()
{
   const std::vector<DeviceSourceMap> &inMaps = DeviceManager::Instance()->GetInputDeviceMaps();
   const std::vector<DeviceSourceMap> &outMaps = DeviceManager::Instance()->GetOutputDeviceMaps();

   wxArrayString hosts;

   // go over our lists add the host to the list if it isn't there yet

   for (auto & device : inMaps) {
      if (!make_iterator_range(hosts).contains(device.hostString)) {
         hosts.push_back(device.hostString);
      }
   }

   for (auto & device : outMaps) {
      if (!make_iterator_range(hosts).contains(device.hostString)) {
         hosts.push_back(device.hostString);
      }
   }

   mHost->Clear();
   mHost->Append(hosts);

   if (hosts.size() == 0) {
      mHost->Enable(false);
   }

   mHost->SetMinSize(wxSize(50, wxDefaultCoord));
}

void DeviceToolBar::FillHostDevices()
{
   const std::vector<DeviceSourceMap> &inMaps  = DeviceManager::Instance()->GetInputDeviceMaps();
   const std::vector<DeviceSourceMap> &outMaps = DeviceManager::Instance()->GetOutputDeviceMaps();

   //read what is in the prefs
   auto host = AudioIOHost.Read();
   int foundHostIndex = -1;

   // if the host is not in the hosts combo then we rescanned.
   // set it to blank so we search for another host.
   if (mHost->FindString(host) == wxNOT_FOUND) {
      host = wxT("");
   }

   for (auto & device : outMaps) {
      if (device.hostString == host) {
         foundHostIndex = device.hostIndex;
         break;
      }
   }

   if (foundHostIndex == -1) {
      for (auto & device : inMaps) {
         if (device.hostString == host) {
            foundHostIndex = device.hostIndex;
            break;
         }
      }
   }

   // If no host was found based on the prefs device host, load the first available one
   if (foundHostIndex == -1) {
      if (outMaps.size()) {
         foundHostIndex = outMaps[0].hostIndex;
      }
      else if (inMaps.size()) {
         foundHostIndex = inMaps[0].hostIndex;
      }
   }

   // Make sure in/out are clear in case no host was found
   mInput->Clear();
   mOutput->Clear();

   // If we still have no host it means no devices, in which case do nothing.
   if (foundHostIndex == -1) {
      return;
   }

   // Repopulate the Input/Output device list available to the user
   for (auto & device : inMaps) {
      if (foundHostIndex == device.hostIndex) {
         mInput->Append(MakeDeviceSourceString(&device));
         if (host.empty()) {
            host = device.hostString;
            AudioIOHost.Write(host);
            mHost->SetStringSelection(host);
         }
      }
   }
   mInput->Enable(mInput->GetCount() ? true : false);

   mInput->SetMinSize(wxSize(50, wxDefaultCoord));

   for (auto & device : outMaps) {
      if (foundHostIndex == device.hostIndex) {
         mOutput->Append(MakeDeviceSourceString(&device));
         if (host.empty()) {
            host = device.hostString;
            AudioIOHost.Write(host);
            gPrefs->Flush();
            mHost->SetStringSelection(host);
         }
      }
   }
   mOutput->Enable(mOutput->GetCount() ? true : false);

   mOutput->SetMinSize(wxSize(50, wxDefaultCoord));

   // The setting of the Device is left up to OnChoice
}

void DeviceToolBar::FillInputChannels()
{
   const std::vector<DeviceSourceMap> &inMaps = DeviceManager::Instance()->GetInputDeviceMaps();
   auto host = AudioIOHost.Read();
   auto device = AudioIORecordingDevice.Read();
   auto source = AudioIORecordingSource.Read();
   long newChannels;

   auto oldChannels = AudioIORecordChannels.Read();
   mInputChannels->Clear();
   for (auto & dev: inMaps) {
      if (source == dev.sourceString &&
          device == dev.deviceString &&
          host   == dev.hostString) {

         // add one selection for each channel of this source
         for (size_t j = 0; j < (unsigned int) dev.numChannels; j++) {
            wxString name;

            if (j == 0) {
               name = _("1 (Mono) Recording Channel");
            }
            else if (j == 1) {
               name = _("2 (Stereo) Recording Channels");
            }
            else {
               name = wxString::Format(wxT("%d"), (int) j + 1);
            }
            mInputChannels->Append(name);
         }
         newChannels = dev.numChannels;
         if (oldChannels <= newChannels && oldChannels >= 1) {
            newChannels = oldChannels;
         }
         if (newChannels >= 1) {
            mInputChannels->SetSelection(newChannels - 1);
         }
         AudioIORecordChannels.Write(newChannels);
         break;
      }
   }
   mInputChannels->Enable(mInputChannels->GetCount() ? true : false);

   mInputChannels->SetMinSize(wxSize(50, wxDefaultCoord));
}

void DeviceToolBar::OnRescannedDevices(DeviceChangeMessage m)
{
   // Hosts may have disappeared or appeared so a complete repopulate is needed.
   if (m == DeviceChangeMessage::Rescan)
      RefillCombos();
}

//return 1 if host changed, 0 otherwise.
int DeviceToolBar::ChangeHost()
{
   int hostSelectionIndex;
   hostSelectionIndex = mHost->GetSelection();

   auto oldHost = AudioIOHost.Read();
   wxString newHost = hostSelectionIndex >= 0 ? mHost->GetString(hostSelectionIndex) :
                                                oldHost;

   if (oldHost == newHost)
      return 0;

   //change the host and switch to correct devices.
   AudioIOHost.Write(newHost);
   gPrefs->Flush();

   // populate the devices
   FillHostDevices();

   return 1;
}

void DeviceToolBar::SetDevices(const DeviceSourceMap *in, const DeviceSourceMap *out)
{
   if (in) {
      AudioIORecordingDevice.Write(in->deviceString);
      AudioIORecordingSourceIndex.Write(in->sourceIndex);
      if (in->totalSources >= 1)
         AudioIORecordingSource.Write(in->sourceString);
      else
         AudioIORecordingSource.Reset();
      gPrefs->Flush();

      FillInputChannels();
   }

   if (out) {
      AudioIOPlaybackDevice.Write(out->deviceString);
      if (out->totalSources >= 1) {
         AudioIOPlaybackSource.Write(out->sourceString);
      } else {
         AudioIOPlaybackSource.Reset();
      }
      gPrefs->Flush();
   }
}

void DeviceToolBar::ChangeDevice(bool isInput)
{
   int newIndex = -1;
   wxChoice *combo = isInput ? mInput :mOutput;
   size_t i;

   int selectionIndex  = combo->GetSelection();
   auto host = AudioIOHost.Read();
   const std::vector<DeviceSourceMap> &maps = isInput ? DeviceManager::Instance()->GetInputDeviceMaps()
                                                      : DeviceManager::Instance()->GetOutputDeviceMaps();

   // Find device indices for input and output
   if (selectionIndex >= 0 ) {
      wxString newDevice = combo->GetStringSelection();
      for (i = 0; i < maps.size(); ++i) {
         wxString name;
         name = MakeDeviceSourceString(&maps[i]);
         if (name == newDevice && maps[i].hostString == host) {
            newIndex = i;
         }
      }
   }

   if (newIndex < 0) {
      wxLogDebug(wxT("DeviceToolBar::OnChoice(): couldn't find device indices"));
      return;
   }

   SetDevices(isInput ? &maps[newIndex] : NULL,
              isInput ? NULL            : &maps[newIndex]);
}

void DeviceToolBar::OnChoice(wxCommandEvent &event)
{
   wxObject *eventObject = event.GetEventObject();
   //if we've changed hosts, we've handled the device switching already.
   if (eventObject == mHost) {
      ChangeHost();
   } else if (eventObject == mInputChannels) {
      int channelsSelectionIndex = mInputChannels->GetSelection();
      if (channelsSelectionIndex >= 0)
         AudioIORecordChannels.Write(channelsSelectionIndex + 1);
   } else if (eventObject == mInput) {
      ChangeDevice(true);
   }
   else if (eventObject == mOutput) {
      ChangeDevice(false);
   }

   auto gAudioIO = AudioIOBase::Get();
   if (gAudioIO) {
      // We cannot have gotten here if gAudioIO->IsAudioTokenActive(),
      // per the setting of AudioIONotBusyFlag and AudioIOBusyFlag in
      // AudacityProject::GetUpdateFlags().
      // However, we can have an invalid audio token (so IsAudioTokenActive()
      // is false), but be monitoring.
      // If monitoring, have to stop the stream, so HandleDeviceChange() can work.
      // We could disable the Preferences command while monitoring, i.e.,
      // set AudioIONotBusyFlag/AudioIOBusyFlag according to monitoring, as well.
      // Instead allow it because unlike recording, for example, monitoring
      // is not clearly something that should prohibit changing device.
      // TODO: We *could* be smarter in this method and call HandleDeviceChange()
      // only when the device choices actually changed. True of lots of prefs!
      // As is, we always stop monitoring before handling the device change.
      if (gAudioIO->IsMonitoring())
      {
         gAudioIO->StopStream();
         while (gAudioIO->IsBusy()) {
            using namespace std::chrono;
            std::this_thread::sleep_for(100ms);
         }
      }
      gAudioIO->HandleDeviceChange();
   }

   PrefsListener::Broadcast(DeviceToolbarPrefsID());
}

void DeviceToolBar::ShowInputDialog()
{
   ShowComboDialog(mInput, XO("Select Recording Device"));
}
void DeviceToolBar::ShowOutputDialog()
{
   ShowComboDialog(mOutput, XO("Select Playback Device"));
}
void DeviceToolBar::ShowHostDialog()
{
   ShowComboDialog(mHost, XO("Select Audio Host"));
}
void DeviceToolBar::ShowChannelsDialog()
{
   ShowComboDialog(mInputChannels, XO("Select Recording Channels"));
}

void DeviceToolBar::ShowComboDialog(wxChoice *combo, const TranslatableString &title)
{
   if (!combo || combo->GetCount() == 0) {
      AudacityMessageBox( XO("Device information is not available.") );
      return;
   }

#if USE_PORTMIXER
   wxArrayStringEx inputSources = combo->GetStrings();

   wxDialogWrapper dlg(nullptr, wxID_ANY, title);
   dlg.SetName();
   ShuttleGui S(&dlg, eIsCreating);
   wxChoice *c;

   S.StartVerticalLay(true);
   {
      S.StartHorizontalLay(wxCENTER, false);
      {
         c = S.AddChoice( Verbatim( combo->GetName() ),
            transform_container<TranslatableStrings>( inputSources, Verbatim ),
            combo->GetSelection());
         c->SetMinSize(c->GetBestSize());
      }
      S.EndHorizontalLay();
   }
   S.EndVerticalLay();
   S.AddStandardButtons();

   dlg.GetSizer()->SetSizeHints(&dlg);
   dlg.Center();

   if (dlg.ShowModal() == wxID_OK)
   {
      wxCommandEvent dummyEvent;
      dummyEvent.SetEventObject(combo);
      // SetSelection() doesn't send an event, so we call OnChoice explicitly
      combo->SetSelection(c->GetSelection());
      OnChoice(dummyEvent);
   }
#endif
}

static RegisteredToolbarFactory factory{
   []( AudacityProject &project ){
      return ToolBar::Holder{ safenew DeviceToolBar{ project } }; }
};

namespace {
AttachedToolBarMenuItem sAttachment{
   /* i18n-hint: Clicking this menu item shows the toolbar
      that manages devices */
   DeviceToolBar::ID(), wxT("ShowDeviceTB"), XXO("&Device Toolbar")
};
}


// Define some related menu items
#include "CommandContext.h"
#include "../CommonCommandFlags.h"

namespace {
void OnInputDevice(const CommandContext &context)
{
   auto &project = context.project;
   auto &tb = DeviceToolBar::Get( project );
   tb.ShowInputDialog();
}

void OnOutputDevice(const CommandContext &context)
{
   auto &project = context.project;
   auto &tb = DeviceToolBar::Get( project );
   tb.ShowOutputDialog();
}

void OnInputChannels(const CommandContext &context)
{
   auto &project = context.project;
   auto &tb = DeviceToolBar::Get( project );
   tb.ShowChannelsDialog();
}

void OnAudioHost(const CommandContext &context)
{
   auto &project = context.project;
   auto &tb = DeviceToolBar::Get( project );
   tb.ShowHostDialog();
}

// Menu definitions

using namespace MenuRegistry;
// Under /MenuBar/Optional/Extra/Part1
auto ExtraDeviceMenu()
{
   static auto menu = std::shared_ptr{
   Menu( wxT("Device"), XXO("De&vice"),
      Command( wxT("InputDevice"), XXO("Change &Recording Device..."),
         OnInputDevice,
         AudioIONotBusyFlag(), wxT("Shift+I") ),
      Command( wxT("OutputDevice"), XXO("Change &Playback Device..."),
         OnOutputDevice,
         AudioIONotBusyFlag(), wxT("Shift+O") ),
      Command( wxT("AudioHost"), XXO("Change Audio &Host..."), OnAudioHost,
         AudioIONotBusyFlag(), wxT("Shift+H") ),
      Command( wxT("InputChannels"), XXO("Change Recording Cha&nnels..."),
         OnInputChannels,
         AudioIONotBusyFlag(), wxT("Shift+N") )
   ) };
   return menu;
}

AttachedItem sAttachment2{ Indirect(ExtraDeviceMenu()),
   Placement{ wxT("Optional/Extra/Part1"), { OrderingHint::End } }
};

}
