1305 lines
36 KiB
C++
1305 lines
36 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
CommandManager.cpp
|
|
|
|
Brian Gunlogson
|
|
Dominic Mazzoni
|
|
|
|
*******************************************************************//**
|
|
|
|
\class CommandManager
|
|
\brief CommandManager implements a system for organizing all user-callable
|
|
commands.
|
|
|
|
It creates and manages a menu bar with a command
|
|
associated with each item, and managing other commands callable
|
|
by keyboard shortcuts.
|
|
|
|
Commands are implemented by overriding an abstract functor class.
|
|
See Menus.cpp for an example use.
|
|
|
|
Menus or submenus containing lists of items can be added at once,
|
|
with a single function (functor) to be called when any of the
|
|
items is selected, with the index number of the selection as the
|
|
parameter. This is useful for dynamic menus (effects) and
|
|
submenus containing a list of choices (selection formats).
|
|
|
|
Menu items can be enabled or disabled individually, groups of
|
|
"multi-items" can be enabled or disabled all at once, or entire
|
|
sets of commands can be enabled or disabled all at once using
|
|
flags. The flags should be a bitfield stored in a 32-bit
|
|
integer but can be whatever you want. You specify both the
|
|
desired values of the flags, and the set of flags relevant to
|
|
a particular command, by using a combination of a flags parameter
|
|
and a mask parameter. Any flag set to 0 in the mask parameter is
|
|
the same as "don't care". Any command whose mask is set to zero
|
|
will not be affected by enabling/disabling by flags.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class CommandFunctor
|
|
\brief CommandFunctor is a very small class that works with
|
|
CommandManager. It holds the callback for one command.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class MenuBarListEntry
|
|
\brief MenuBarListEntry is a structure used by CommandManager.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class SubMenuListEntry
|
|
\brief SubMenuListEntry is a structure used by CommandManager.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class CommandListEntry
|
|
\brief CommandListEntry is a structure used by CommandManager.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class MenuBarList
|
|
\brief List of MenuBarListEntry.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class SubMenuList
|
|
\brief List of SubMenuListEntry.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class CommandList
|
|
\brief List of CommandListEntry.
|
|
|
|
*//******************************************************************/
|
|
|
|
#include "../Audacity.h"
|
|
|
|
#include <wx/defs.h>
|
|
#include <wx/hash.h>
|
|
#include <wx/intl.h>
|
|
#include <wx/log.h>
|
|
#include <wx/msgdlg.h>
|
|
#include <wx/tokenzr.h>
|
|
|
|
#include "../AudacityApp.h"
|
|
#include "../Prefs.h"
|
|
#include "../Project.h"
|
|
|
|
#include "CommandManager.h"
|
|
|
|
#include "Keyboard.h"
|
|
#include "../effects/EffectManager.h"
|
|
|
|
// On wxGTK, there may be many many many plugins, but the menus don't automatically
|
|
// allow for scrolling, so we build sub-menus. If the menu gets longer than
|
|
// MAX_MENU_LEN, we put things in submenus that have MAX_SUBMENU_LEN items in them.
|
|
//
|
|
#ifdef __WXGTK__
|
|
#define MAX_MENU_LEN 20
|
|
#define MAX_SUBMENU_LEN 15
|
|
#else
|
|
#define MAX_MENU_LEN 1000
|
|
#define MAX_SUBMENU_LEN 1000
|
|
#endif
|
|
|
|
#define COMMAND _("Command")
|
|
///
|
|
/// Standard Constructor
|
|
///
|
|
CommandManager::CommandManager():
|
|
mCurrentID(0),
|
|
mHiddenID(0),
|
|
mCurrentMenuName(COMMAND),
|
|
mCurrentMenu(NULL),
|
|
mOpenMenu(NULL),
|
|
mDefaultFlags(0),
|
|
mDefaultMask(0)
|
|
{
|
|
mbSeparatorAllowed = false;
|
|
mbHideFlaggedItems = true;
|
|
mHidingLevel = 0;
|
|
}
|
|
|
|
///
|
|
/// Class Destructor. Includes PurgeData, which removes
|
|
/// menubars
|
|
CommandManager::~CommandManager()
|
|
{
|
|
//WARNING: This removes menubars that could still be assigned to windows!
|
|
PurgeData();
|
|
}
|
|
|
|
void CommandManager::PurgeData()
|
|
{
|
|
// Delete callback functors BEFORE clearing mCommandList!
|
|
// These are the items created within 'FN()'
|
|
size_t i;
|
|
CommandFunctor * pCallback = NULL;
|
|
for(i=0; i<mCommandList.GetCount(); i++)
|
|
{
|
|
CommandListEntry *tmpEntry = mCommandList[i];
|
|
// JKC: We only want to delete each callbacks once.
|
|
// AddItemList() may have inserted the same callback
|
|
// several times over.
|
|
if( tmpEntry->callback != pCallback )
|
|
{
|
|
pCallback = tmpEntry->callback;
|
|
delete pCallback;
|
|
}
|
|
}
|
|
|
|
// mCommandList contains pointers to CommandListEntrys
|
|
// mMenuBarList contains pointers to MenuBarListEntrys.
|
|
// mSubMenuList contains pointers to SubMenuListEntrys
|
|
WX_CLEAR_ARRAY( mCommandList );
|
|
WX_CLEAR_ARRAY( mMenuBarList );
|
|
WX_CLEAR_ARRAY( mSubMenuList );
|
|
|
|
mCommandNameHash.clear();
|
|
mCommandKeyHash.clear();
|
|
mCommandIDHash.clear();
|
|
|
|
mCurrentMenu = NULL;
|
|
mCurrentMenuName = COMMAND;
|
|
mCurrentID = 0;
|
|
}
|
|
|
|
|
|
///
|
|
/// Makes a new menubar for placement on the top of a project
|
|
/// Names it according to the passed-in string argument.
|
|
///
|
|
/// If the menubar already exists, simply returns it.
|
|
wxMenuBar *CommandManager::AddMenuBar(wxString sMenu)
|
|
{
|
|
mHidingLevel = 0;
|
|
wxMenuBar *menuBar = GetMenuBar(sMenu);
|
|
if (menuBar)
|
|
return menuBar;
|
|
|
|
MenuBarListEntry *tmpEntry = new MenuBarListEntry;
|
|
|
|
tmpEntry->menubar = new wxMenuBar();
|
|
tmpEntry->name = sMenu;
|
|
|
|
mMenuBarList.Add(tmpEntry);
|
|
|
|
return tmpEntry->menubar;
|
|
}
|
|
|
|
|
|
///
|
|
/// Retrieves the menubar based on the name given in AddMenuBar(name)
|
|
///
|
|
wxMenuBar * CommandManager::GetMenuBar(wxString sMenu)
|
|
{
|
|
for(unsigned int i = 0; i < mMenuBarList.GetCount(); i++)
|
|
{
|
|
if(!mMenuBarList[i]->name.Cmp(sMenu))
|
|
return mMenuBarList[i]->menubar;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
///
|
|
/// Retrieve the 'current' menubar; either NULL or the
|
|
/// last on in the mMenuBarList.
|
|
wxMenuBar * CommandManager::CurrentMenuBar()
|
|
{
|
|
if(mMenuBarList.IsEmpty())
|
|
return NULL;
|
|
|
|
return mMenuBarList[mMenuBarList.GetCount()-1]->menubar;
|
|
}
|
|
|
|
|
|
///
|
|
/// This makes a new menu and adds it to the 'CurrentMenuBar'
|
|
///
|
|
/// If the menu already exists, all of the items in it are
|
|
/// cleared instead.
|
|
///
|
|
void CommandManager::BeginMenu(wxString tNameIn)
|
|
{
|
|
|
|
wxString tName = tNameIn;
|
|
if( ItemShouldBeHidden( tName ) )
|
|
{
|
|
mHidingLevel++;
|
|
return;
|
|
}
|
|
|
|
wxMenu *tmpMenu = new wxMenu();
|
|
|
|
mCurrentMenu = tmpMenu;
|
|
mCurrentMenuName = tName;
|
|
|
|
CurrentMenuBar()->Append(mCurrentMenu, tName);
|
|
}
|
|
|
|
|
|
///
|
|
/// This ends a menu by setting the pointer to it
|
|
/// to NULL. It is still attached to the CurrentMenuBar()
|
|
void CommandManager::EndMenu()
|
|
{
|
|
if( mHidingLevel > 0 )
|
|
mHidingLevel--;
|
|
|
|
mCurrentMenu = NULL;
|
|
mCurrentMenuName = COMMAND;
|
|
}
|
|
|
|
|
|
///
|
|
/// This starts a new submenu, and names it according to
|
|
/// the function's argument.
|
|
wxMenu* CommandManager::BeginSubMenu(wxString tNameIn)
|
|
{
|
|
wxString tName = tNameIn;
|
|
if( ItemShouldBeHidden( tName ) )
|
|
{
|
|
mHidingLevel++;
|
|
return NULL;
|
|
}
|
|
|
|
SubMenuListEntry *tmpEntry = new SubMenuListEntry;
|
|
|
|
tmpEntry->menu = new wxMenu();
|
|
tmpEntry->name = tName;
|
|
|
|
mSubMenuList.Add(tmpEntry);
|
|
mbSeparatorAllowed = false;
|
|
|
|
return(tmpEntry->menu);
|
|
}
|
|
|
|
|
|
///
|
|
/// This function is called after the final item of a SUBmenu is added.
|
|
/// Submenu items are added just like regular menu items; they just happen
|
|
/// after BeginSubMenu() is called but before EndSubMenu() is called.
|
|
void CommandManager::EndSubMenu()
|
|
{
|
|
if( mHidingLevel > 0 )
|
|
{
|
|
mHidingLevel--;
|
|
return;
|
|
}
|
|
|
|
size_t submenu_count = mSubMenuList.GetCount()-1;
|
|
|
|
//Save the submenu's information
|
|
SubMenuListEntry *tmpSubMenu = mSubMenuList[submenu_count];
|
|
|
|
//Pop off the new submenu so CurrentMenu returns the parent of the submenu
|
|
mSubMenuList.RemoveAt(submenu_count);
|
|
|
|
//Add the submenu to the current menu
|
|
CurrentMenu()->Append(0, tmpSubMenu->name, tmpSubMenu->menu, tmpSubMenu->name);
|
|
mbSeparatorAllowed = true;
|
|
|
|
delete tmpSubMenu;
|
|
}
|
|
|
|
|
|
///
|
|
/// This returns the 'Current' Submenu, which is the one at the
|
|
/// end of the mSubMenuList (or NULL, if it doesn't exist).
|
|
wxMenu * CommandManager::CurrentSubMenu()
|
|
{
|
|
if(mSubMenuList.IsEmpty())
|
|
return NULL;
|
|
|
|
return mSubMenuList[mSubMenuList.GetCount()-1]->menu;
|
|
}
|
|
|
|
///
|
|
/// This returns the current menu that we're appending to - note that
|
|
/// it could be a submenu if BeginSubMenu was called and we haven't
|
|
/// reached EndSubMenu yet.
|
|
wxMenu * CommandManager::CurrentMenu()
|
|
{
|
|
if(!mCurrentMenu)
|
|
return NULL;
|
|
|
|
wxMenu * tmpCurrentSubMenu = CurrentSubMenu();
|
|
|
|
if(!tmpCurrentSubMenu)
|
|
{
|
|
return mCurrentMenu;
|
|
}
|
|
|
|
return tmpCurrentSubMenu;
|
|
}
|
|
|
|
// This allows us a simplified menu that has fewer items.
|
|
bool CommandManager::ItemShouldBeHidden( wxString &Label )
|
|
{
|
|
if( Label.StartsWith(wxT("!")) )
|
|
{
|
|
Label = Label.Mid( 1 );
|
|
if( mbHideFlaggedItems )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if( mHidingLevel > 0 )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
///
|
|
/// Add a menu item to the current menu. When the user selects it, the
|
|
/// given functor will be called
|
|
void CommandManager::InsertItem(wxString name, wxString label_in,
|
|
CommandFunctor *callback, wxString after,
|
|
int checkmark)
|
|
{
|
|
wxString label = label_in;
|
|
|
|
if (ItemShouldBeHidden(label)) {
|
|
delete callback;
|
|
return;
|
|
}
|
|
|
|
wxMenuBar *bar = GetActiveProject()->GetMenuBar();
|
|
wxArrayString names = ::wxStringTokenize(after, wxT(":"));
|
|
size_t cnt = names.GetCount();
|
|
|
|
if (cnt < 2) {
|
|
return;
|
|
}
|
|
|
|
int pos = bar->FindMenu(names[0]);
|
|
if (pos == wxNOT_FOUND) {
|
|
return;
|
|
}
|
|
|
|
wxMenu *menu = bar->GetMenu(pos);
|
|
wxMenuItem *item = NULL;
|
|
pos = 0;
|
|
|
|
for (size_t ndx = 1; ndx < cnt; ndx++) {
|
|
wxMenuItemList list = menu->GetMenuItems();
|
|
size_t lcnt = list.GetCount();
|
|
wxString label = names[ndx];
|
|
|
|
for (size_t lndx = 0; lndx < lcnt; lndx++) {
|
|
item = list.Item(lndx)->GetData();
|
|
if (item->GetLabel() == label) {
|
|
break;
|
|
}
|
|
pos++;
|
|
item = NULL;
|
|
}
|
|
|
|
if (item == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (item->IsSubMenu()) {
|
|
menu = item->GetSubMenu();
|
|
item = NULL;
|
|
continue;
|
|
}
|
|
|
|
if (ndx + 1 != cnt) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
int ID = NewIdentifier(name, label, menu, callback, false, 0, 0);
|
|
|
|
// Replace the accel key with the one from the preferences
|
|
label = label.BeforeFirst(wxT('\t'));
|
|
|
|
// This is a very weird hack. Under GTK, menu labels are totally
|
|
// linked to accelerators the first time you create a menu item
|
|
// with that label and can't be changed. This causes all sorts of
|
|
// problems. As a workaround, we create each menu item with a
|
|
// made-up name (just an ID number string) but with the accelerator
|
|
// we want, then immediately change the label to the correct string.
|
|
// -DMM
|
|
mHiddenID++;
|
|
wxString dummy, newLabel;
|
|
dummy.Printf(wxT("%s%08d"), label.c_str(), mHiddenID);
|
|
newLabel = label;
|
|
|
|
if (checkmark >= 0) {
|
|
menu->InsertCheckItem(pos, ID, dummy);
|
|
menu->Check(ID, checkmark != 0);
|
|
}
|
|
else {
|
|
menu->Insert(pos, ID, dummy);
|
|
}
|
|
menu->SetLabel(ID, newLabel);
|
|
|
|
mbSeparatorAllowed = true;
|
|
}
|
|
|
|
void CommandManager::AddCheck(const wxChar *name,
|
|
const wxChar *label,
|
|
CommandFunctor *callback,
|
|
int checkmark)
|
|
{
|
|
AddItem(name, label, callback, wxT(""), NoFlagsSpecifed, NoFlagsSpecifed, checkmark);
|
|
}
|
|
|
|
void CommandManager::AddCheck(const wxChar *name,
|
|
const wxChar *label,
|
|
CommandFunctor *callback,
|
|
int checkmark,
|
|
unsigned int flags,
|
|
unsigned int mask)
|
|
{
|
|
AddItem(name, label, callback, wxT(""), flags, mask, checkmark);
|
|
}
|
|
|
|
void CommandManager::AddItem(const wxChar *name,
|
|
const wxChar *label,
|
|
CommandFunctor *callback,
|
|
unsigned int flags,
|
|
unsigned int mask)
|
|
{
|
|
AddItem(name, label, callback, wxT(""), flags, mask);
|
|
}
|
|
|
|
void CommandManager::AddItem(const wxChar *name,
|
|
const wxChar *label_in,
|
|
CommandFunctor *callback,
|
|
const wxChar *accel,
|
|
unsigned int flags,
|
|
unsigned int mask,
|
|
int checkmark)
|
|
{
|
|
wxString label(label_in);
|
|
label += wxT("\t");
|
|
label += accel;
|
|
|
|
if (ItemShouldBeHidden(label)) {
|
|
delete callback;
|
|
return;
|
|
}
|
|
|
|
int ID = NewIdentifier(name, label, CurrentMenu(), callback, false, 0, 0);
|
|
|
|
if (flags != NoFlagsSpecifed || mask != NoFlagsSpecifed) {
|
|
SetCommandFlags(name, flags, mask);
|
|
}
|
|
|
|
// Replace the accel key with the one from the preferences
|
|
label = label.BeforeFirst(wxT('\t'));
|
|
|
|
// This is a very weird hack. Under GTK, menu labels are totally
|
|
// linked to accelerators the first time you create a menu item
|
|
// with that label and can't be changed. This causes all sorts of
|
|
// problems. As a workaround, we create each menu item with a
|
|
// made-up name (just an ID number string) but with the accelerator
|
|
// we want, then immediately change the label to the correct string.
|
|
// -DMM
|
|
wxString newLabel;
|
|
newLabel.Printf(wxT("%s%08d"), label.c_str(), ++mHiddenID);
|
|
|
|
if (checkmark >= 0) {
|
|
CurrentMenu()->AppendCheckItem(ID, newLabel);
|
|
CurrentMenu()->Check(ID, checkmark != 0);
|
|
}
|
|
else {
|
|
CurrentMenu()->Append(ID, newLabel);
|
|
}
|
|
|
|
CurrentMenu()->SetLabel(ID, label);
|
|
mbSeparatorAllowed = true;
|
|
}
|
|
|
|
///
|
|
/// Add a list of menu items to the current menu. When the user selects any
|
|
/// one of these, the given functor will be called
|
|
/// with its position in the list as the index number.
|
|
/// When you call Enable on this command name, it will enable or disable
|
|
/// all of the items at once.
|
|
void CommandManager::AddItemList(wxString name, wxArrayString labels,
|
|
CommandFunctor *callback,
|
|
bool plugins /*= false*/)
|
|
{
|
|
unsigned int i;
|
|
|
|
#ifndef __WXGTK__
|
|
plugins = false;
|
|
#endif
|
|
|
|
if( mHidingLevel > 0 )
|
|
return;
|
|
|
|
if (CurrentMenu()->GetMenuItemCount() + labels.GetCount() < MAX_MENU_LEN)
|
|
plugins = false;
|
|
|
|
if (!plugins) {
|
|
for(i=0; i<labels.GetCount(); i++) {
|
|
int ID = NewIdentifier(name, labels[i], CurrentMenu(), callback,
|
|
true, i, labels.GetCount());
|
|
CurrentMenu()->Append(ID, labels[i]);
|
|
}
|
|
mbSeparatorAllowed = true;
|
|
return;
|
|
}
|
|
|
|
wxString label;
|
|
unsigned int effLen = labels.GetCount();
|
|
int listnum = 1;
|
|
int tmpmax = MAX_SUBMENU_LEN < effLen? MAX_SUBMENU_LEN: effLen;
|
|
|
|
//The first submenu starts at 1.
|
|
BeginSubMenu(wxString::Format(_("Plugins 1 to %i"), tmpmax));
|
|
|
|
for(i=0; i<effLen; i++) {
|
|
int ID = NewIdentifier(name, labels[i], CurrentMenu(), callback,
|
|
true, i, effLen);
|
|
|
|
CurrentMenu()->Append(ID, labels[i]);
|
|
|
|
if(((i+1) % MAX_SUBMENU_LEN) == 0 && i != (effLen - 1)) {
|
|
EndSubMenu();
|
|
listnum++;
|
|
|
|
//This label the plugins by number in the submenu title (1 to 15, 15 to 30, etc.)
|
|
tmpmax = i + MAX_SUBMENU_LEN < effLen? 1 + i + MAX_SUBMENU_LEN: effLen;
|
|
BeginSubMenu(wxString::Format(_("Plugins %i to %i"),i+2,tmpmax));
|
|
}
|
|
}
|
|
EndSubMenu();
|
|
}
|
|
|
|
///
|
|
/// Add a command that doesn't appear in a menu. When the key is pressed, the
|
|
/// given function pointer will be called (via the CommandManagerListener)
|
|
void CommandManager::AddCommand(const wxChar *name,
|
|
const wxChar *label,
|
|
CommandFunctor *callback,
|
|
unsigned int flags,
|
|
unsigned int mask)
|
|
{
|
|
AddCommand(name, label, callback, wxT(""), flags, mask);
|
|
}
|
|
|
|
void CommandManager::AddCommand(const wxChar *name,
|
|
const wxChar *label_in,
|
|
CommandFunctor *callback,
|
|
const wxChar *accel,
|
|
unsigned int flags,
|
|
unsigned int mask)
|
|
{
|
|
wxString label(label_in);
|
|
label += wxT("\t");
|
|
label += accel;
|
|
|
|
NewIdentifier(name, label, NULL, callback, false, 0, 0);
|
|
|
|
if (flags != NoFlagsSpecifed || mask != NoFlagsSpecifed) {
|
|
SetCommandFlags(name, flags, mask);
|
|
}
|
|
}
|
|
|
|
void CommandManager::AddSeparator()
|
|
{
|
|
if( mHidingLevel > 0 )
|
|
return;
|
|
if( mbSeparatorAllowed )
|
|
CurrentMenu()->AppendSeparator();
|
|
mbSeparatorAllowed = false; // boolean to prevent too many separators.
|
|
}
|
|
|
|
int CommandManager::NextIdentifier(int ID)
|
|
{
|
|
ID++;
|
|
|
|
//Skip the reserved identifiers used by wxWidgets
|
|
if((ID >= wxID_LOWEST) && (ID <= wxID_HIGHEST))
|
|
ID = wxID_HIGHEST+1;
|
|
|
|
return ID;
|
|
}
|
|
|
|
///Given all of the information for a command, comes up with a new unique
|
|
///ID, adds it to a list, and returns the ID.
|
|
///WARNING: Does this conflict with the identifiers set for controls/windows?
|
|
///If it does, a workaround may be to keep controls below wxID_LOWEST
|
|
///and keep menus above wxID_HIGHEST
|
|
int CommandManager::NewIdentifier(wxString name, wxString label, wxMenu *menu,
|
|
CommandFunctor *callback,
|
|
bool multi, int index, int count)
|
|
{
|
|
CommandListEntry *tmpEntry = new CommandListEntry;
|
|
|
|
wxString labelPrefix;
|
|
if (!mSubMenuList.IsEmpty()) {
|
|
labelPrefix = mSubMenuList[mSubMenuList.GetCount() - 1]->name;
|
|
}
|
|
|
|
// wxMac 2.5 and higher will do special things with the
|
|
// Preferences, Exit (Quit), and About menu items,
|
|
// if we give them the right IDs.
|
|
// Otherwise we just pick increasing ID numbers for each new
|
|
// command. Note that the name string we are comparing
|
|
// ("About", "Preferences") is the internal command name
|
|
// (untranslated), not the label that actually appears in the
|
|
// menu (which might be translated).
|
|
|
|
mCurrentID = NextIdentifier(mCurrentID);
|
|
tmpEntry->id = mCurrentID;
|
|
tmpEntry->key = GetKey(label);
|
|
|
|
#if defined(__WXMAC__)
|
|
if (name == wxT("Preferences"))
|
|
tmpEntry->id = wxID_PREFERENCES;
|
|
else if (name == wxT("Exit"))
|
|
tmpEntry->id = wxID_EXIT;
|
|
else if (name == wxT("About"))
|
|
tmpEntry->id = wxID_ABOUT;
|
|
#endif
|
|
|
|
tmpEntry->defaultKey = tmpEntry->key;
|
|
tmpEntry->name = name;
|
|
tmpEntry->label = label;
|
|
tmpEntry->labelPrefix = labelPrefix;
|
|
tmpEntry->labelTop = wxMenuItem::GetLabelFromText(mCurrentMenuName);
|
|
tmpEntry->menu = menu;
|
|
tmpEntry->callback = callback;
|
|
tmpEntry->multi = multi;
|
|
tmpEntry->index = index;
|
|
tmpEntry->count = count;
|
|
tmpEntry->flags = mDefaultFlags;
|
|
tmpEntry->mask = mDefaultMask;
|
|
tmpEntry->enabled = true;
|
|
|
|
// Key from preferences overridse the default key given
|
|
gPrefs->SetPath(wxT("/NewKeys"));
|
|
if (gPrefs->HasEntry(name)) {
|
|
tmpEntry->key = KeyStringNormalize(gPrefs->Read(name, tmpEntry->key));
|
|
}
|
|
gPrefs->SetPath(wxT("/"));
|
|
|
|
mCommandList.Add(tmpEntry);
|
|
mCommandIDHash[tmpEntry->id] = tmpEntry;
|
|
|
|
if (index == 0 || !multi) {
|
|
#if defined(__WXDEBUG__)
|
|
CommandListEntry *prev = mCommandNameHash[name];
|
|
if (prev) {
|
|
wxLogDebug(wxT("Command '%s' defined by '%s' and '%s'"),
|
|
name.c_str(),
|
|
prev->label.BeforeFirst(wxT('\t')).c_str(),
|
|
tmpEntry->label.BeforeFirst(wxT('\t')).c_str());
|
|
wxASSERT(!prev);
|
|
}
|
|
#endif
|
|
mCommandNameHash[name] = tmpEntry;
|
|
}
|
|
|
|
if (tmpEntry->key != wxT("")) {
|
|
mCommandKeyHash[tmpEntry->key] = tmpEntry;
|
|
}
|
|
|
|
return tmpEntry->id;
|
|
}
|
|
|
|
wxString CommandManager::GetKey(wxString label)
|
|
{
|
|
wxString key = label.AfterFirst(wxT('\t')).BeforeFirst(wxT('\t'));
|
|
if (key.IsEmpty()) {
|
|
return key;
|
|
}
|
|
|
|
return KeyStringNormalize(key);
|
|
}
|
|
|
|
///Enables or disables a menu item based on its name (not the
|
|
///label in the menu bar, but the name of the command.)
|
|
///If you give it the name of a multi-item (one that was
|
|
///added using AddItemList(), it will enable or disable all
|
|
///of them at once
|
|
void CommandManager::Enable(CommandListEntry *entry, bool enabled)
|
|
{
|
|
if (!entry->menu) {
|
|
entry->enabled = enabled;
|
|
return;
|
|
}
|
|
|
|
// LL: Refresh from real state as we can get out of sync on the
|
|
// Mac due to its reluctance to enable menus when in a modal
|
|
// state.
|
|
entry->enabled = entry->menu->IsEnabled(entry->id);
|
|
|
|
// Only enabled if needed
|
|
if (entry->enabled != enabled) {
|
|
entry->menu->Enable(entry->id, enabled);
|
|
entry->enabled = entry->menu->IsEnabled(entry->id);
|
|
}
|
|
|
|
if (entry->multi) {
|
|
int i;
|
|
int ID = entry->id;
|
|
|
|
for(i=1; i<entry->count; i++) {
|
|
ID = NextIdentifier(ID);
|
|
|
|
// This menu item is not necessarily in the same menu, because
|
|
// multi-items can be spread across multiple sub menus
|
|
CommandListEntry *multiEntry = mCommandIDHash[ID];
|
|
if (multiEntry) {
|
|
wxMenuItem *item = multiEntry->menu->FindItem(ID);
|
|
|
|
if (item) {
|
|
item->Enable(enabled);
|
|
} else {
|
|
wxLogDebug(wxT("Warning: Menu entry with id %i in %s not found"),
|
|
ID, (const wxChar*)entry->name);
|
|
}
|
|
} else {
|
|
wxLogDebug(wxT("Warning: Menu entry with id %i not in hash"), ID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CommandManager::Enable(wxString name, bool enabled)
|
|
{
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
if (!entry || !entry->menu) {
|
|
wxLogDebug(wxT("Warning: Unknown command enabled: '%s'"),
|
|
(const wxChar*)name);
|
|
return;
|
|
}
|
|
|
|
Enable(entry, enabled);
|
|
}
|
|
|
|
void CommandManager::EnableUsingFlags(wxUint32 flags, wxUint32 mask)
|
|
{
|
|
unsigned int i;
|
|
|
|
for(i=0; i<mCommandList.GetCount(); i++) {
|
|
CommandListEntry *entry = mCommandList[i];
|
|
if (entry->multi && entry->index != 0)
|
|
continue;
|
|
|
|
wxUint32 combinedMask = (mask & entry->mask);
|
|
if (combinedMask) {
|
|
bool enable = ((flags & combinedMask) ==
|
|
(entry->flags & combinedMask));
|
|
Enable(entry, enable);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CommandManager::GetEnabled(const wxString &name)
|
|
{
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
if (!entry || !entry->menu) {
|
|
wxLogDebug(wxT("Warning: command doesn't exist: '%s'"),
|
|
(const wxChar*)name);
|
|
return false;
|
|
}
|
|
return entry->enabled;
|
|
}
|
|
|
|
void CommandManager::Check(wxString name, bool checked)
|
|
{
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
if (!entry || !entry->menu) {
|
|
return;
|
|
}
|
|
|
|
entry->menu->Check(entry->id, checked);
|
|
}
|
|
|
|
///Changes the label text of a menu item
|
|
void CommandManager::Modify(wxString name, wxString newLabel)
|
|
{
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
if (entry && entry->menu) {
|
|
newLabel = newLabel.BeforeFirst(wxT('\t'));
|
|
if (!entry->key.IsEmpty())
|
|
newLabel = newLabel + wxT("\t") + entry->key;
|
|
entry->label = newLabel;
|
|
entry->menu->SetLabel(entry->id, newLabel);
|
|
}
|
|
}
|
|
|
|
void CommandManager::SetKeyFromName(wxString name, wxString key)
|
|
{
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
if (entry) {
|
|
entry->key = KeyStringNormalize(key);
|
|
}
|
|
}
|
|
|
|
void CommandManager::HandleMenuOpen(wxMenuEvent &evt)
|
|
{
|
|
// Ensure we have a menu and that it's a top-level menu.
|
|
wxMenu *m = evt.GetMenu();
|
|
if (!m || m->GetParent())
|
|
return;
|
|
|
|
// Windows does not send a CLOSE event if you move from one
|
|
// top-level menu to another, so simulate it.
|
|
#if !defined(__WXMAC__)
|
|
if (mOpenMenu) {
|
|
wxMenuEvent dummy;
|
|
HandleMenuClose(dummy);
|
|
}
|
|
|
|
// Remember this menu
|
|
mOpenMenu = m;
|
|
#endif
|
|
|
|
// Turn on the accelerators
|
|
ToggleAccels(m, true);
|
|
|
|
return;
|
|
}
|
|
|
|
void CommandManager::HandleMenuClose(wxMenuEvent &evt)
|
|
{
|
|
#if defined(__WXMAC__)
|
|
mOpenMenu = evt.GetMenu();
|
|
#endif
|
|
|
|
// This can happen when if the Windows system menu is used
|
|
if (mOpenMenu == NULL)
|
|
return;
|
|
|
|
// GetMenu() under Windows will always return NULL. And on other
|
|
// platforms we must ensure we are a top-level menu.
|
|
wxMenu *m = evt.GetMenu();
|
|
if (m && m->GetParent())
|
|
return;
|
|
|
|
// Turn off the accelerators
|
|
ToggleAccels(mOpenMenu, false);
|
|
|
|
// Forget about it
|
|
mOpenMenu = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
void CommandManager::ToggleAccels(wxMenu *m, bool show)
|
|
{
|
|
// Add the top-level menu to the stack;
|
|
wxArrayPtrVoid stack;
|
|
stack.Add(m);
|
|
|
|
// Process all sub-menus in this tree
|
|
while (!stack.IsEmpty()) {
|
|
|
|
// Pop the bottom entry
|
|
m = (wxMenu *) stack.Item(0);
|
|
stack.RemoveAt(0);
|
|
|
|
// Retrieve menuitem info for this menu
|
|
wxMenuItemList mil = m->GetMenuItems();
|
|
int iCnt = m->GetMenuItemCount();
|
|
int iNdx;
|
|
|
|
// Iterate all menuitems at this level
|
|
for (iNdx = 0; iNdx < iCnt; iNdx++) {
|
|
|
|
// Retrieve the menuitem
|
|
wxMenuItem *mi = mil.Item(iNdx)->GetData();
|
|
if (!mi)
|
|
continue;
|
|
|
|
// Stack the menu if this item represents a submenu
|
|
if (mi->IsSubMenu()) {
|
|
stack.Add(mi->GetSubMenu());
|
|
continue;
|
|
}
|
|
|
|
// Retrieve the command entry for this item
|
|
CommandListEntry *entry = mCommandIDHash[mi->GetId()];
|
|
if (!entry)
|
|
continue;
|
|
|
|
// Rebuild the label based on whether the accelerator should
|
|
// be shown.
|
|
wxString label = entry->label.BeforeFirst(wxT('\t'));
|
|
if (show && !entry->key.IsEmpty()) {
|
|
label = label + wxT("\t") + entry->key;
|
|
}
|
|
|
|
// Set the new label
|
|
mi->SetText( label );
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void CommandManager::TellUserWhyDisallowed( wxUint32 flagsGot, wxUint32 flagsRequired )
|
|
{
|
|
// The default string for 'reason' is a catch all. I hope it won't ever be seen
|
|
// and that we will get something more specific.
|
|
wxString reason = _("There was a problem with your last action. If you think\nthis is a bug, please tell us exactly where it occurred.");
|
|
|
|
wxUint32 missingFlags = flagsRequired & (~flagsGot );
|
|
if( missingFlags & AudioIONotBusyFlag )
|
|
reason= _("You can only do this when playing and recording are\n stopped. [Pausing is not sufficient.]");
|
|
else if( missingFlags & StereoRequiredFlag )
|
|
reason = _("You must first select some stereo audio for this\n to use. [You can't use this with mono.]");
|
|
else if( missingFlags & TimeSelectedFlag )
|
|
reason = _("You must first select some audio for this to use.");
|
|
else if( missingFlags & WaveTracksSelectedFlag)
|
|
reason = _("You must first select some audio for this\n to use. [Selecting other kinds of track won't work.]");
|
|
// If the only thing wrong was no tracks, we do nothing and don't report a problem
|
|
else if( missingFlags == TracksExistFlag )
|
|
return;
|
|
|
|
wxMessageBox(reason, _("Disallowed"), wxICON_WARNING | wxOK );
|
|
}
|
|
|
|
/// HandleCommandEntry() takes a CommandListEntry and executes it
|
|
/// returning true iff successful. If you pass any flags,
|
|
///the command won't be executed unless the flags are compatible
|
|
///with the command's flags.
|
|
bool CommandManager::HandleCommandEntry(CommandListEntry * entry, wxUint32 flags, wxUint32 mask)
|
|
{
|
|
if (!entry || !entry->enabled)
|
|
return false;
|
|
|
|
wxUint32 combinedMask = (mask & entry->mask);
|
|
if (combinedMask) {
|
|
|
|
AudacityProject * proj;
|
|
proj = GetActiveProject();
|
|
wxASSERT( proj );
|
|
if( !proj )
|
|
return false;
|
|
|
|
// NB: The call may have the side effect of changing flags.
|
|
bool allowed = proj->TryToMakeActionAllowed( flags, entry->flags, combinedMask );
|
|
|
|
if (!allowed)
|
|
{
|
|
TellUserWhyDisallowed(
|
|
flags & combinedMask, entry->flags & combinedMask);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
(*(entry->callback))(entry->index);
|
|
|
|
return true;
|
|
}
|
|
|
|
///Call this when a menu event is received.
|
|
///If it matches a command, it will call the appropriate
|
|
///CommandManagerListener function. If you pass any flags,
|
|
///the command won't be executed unless the flags are compatible
|
|
///with the command's flags.
|
|
bool CommandManager::HandleMenuID(int id, wxUint32 flags, wxUint32 mask)
|
|
{
|
|
CommandListEntry *entry = mCommandIDHash[id];
|
|
return HandleCommandEntry( entry, flags, mask );
|
|
}
|
|
|
|
///Call this when a key event is received.
|
|
///If it matches a command, it will call the appropriate
|
|
///CommandManagerListener function. If you pass any flags,
|
|
///the command won't be executed unless the flags are compatible
|
|
///with the command's flags.
|
|
bool CommandManager::HandleKey(wxKeyEvent &evt, wxUint32 flags, wxUint32 mask)
|
|
{
|
|
wxString keyStr = KeyEventToKeyString(evt);
|
|
CommandListEntry *entry = mCommandKeyHash[keyStr];
|
|
return HandleCommandEntry( entry, flags, mask );
|
|
}
|
|
|
|
/// HandleTextualCommand() allows us a limitted version of script/batch
|
|
/// behavior, since we can get from a string command name to the actual
|
|
/// code to run.
|
|
bool CommandManager::HandleTextualCommand(wxString & Str, wxUint32 flags, wxUint32 mask)
|
|
{
|
|
unsigned int i;
|
|
|
|
// Linear search for now...
|
|
for(i=0; i<mCommandList.GetCount(); i++) {
|
|
if (!mCommandList[i]->multi)
|
|
{
|
|
if( Str.IsSameAs( mCommandList[i]->name ))
|
|
{
|
|
return HandleCommandEntry( mCommandList[i], flags, mask);
|
|
}
|
|
}
|
|
}
|
|
// Not one of the singleton commands.
|
|
// We could/should try all the list-style commands.
|
|
// instead we only try the effects.
|
|
EffectArray *effects;
|
|
AudacityProject * proj;
|
|
proj = GetActiveProject();
|
|
if( !proj )
|
|
return false;
|
|
|
|
int effectFlags = ALL_EFFECTS | CONFIGURED_EFFECT;
|
|
effects = EffectManager::Get().GetEffects(effectFlags);
|
|
for(i=0; i<effects->GetCount(); i++) {
|
|
wxString effectName = (*effects)[i]->GetEffectName();
|
|
if( Str.IsSameAs( effectName ))
|
|
{
|
|
return proj->OnEffect( effectFlags, (*effects)[i] );
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CommandManager::GetCategories(wxArrayString &cats)
|
|
{
|
|
cats.Clear();
|
|
|
|
size_t cnt = mCommandList.GetCount();
|
|
|
|
for (size_t i = 0; i < cnt; i++) {
|
|
wxString cat = mCommandList[i]->labelTop;
|
|
if (cats.Index(cat) == wxNOT_FOUND) {
|
|
cats.Add(cat);
|
|
}
|
|
}
|
|
#if 0
|
|
mCommandList.GetCount(); i++) {
|
|
if (includeMultis || !mCommandList[i]->multi)
|
|
names.Add(mCommandList[i]->name);
|
|
}
|
|
|
|
AudacityProject *p = GetActiveProject();
|
|
if (p == NULL) {
|
|
return;
|
|
}
|
|
|
|
wxMenuBar *bar = p->GetMenuBar();
|
|
size_t cnt = bar->GetMenuCount();
|
|
for (size_t i = 0; i < cnt; i++) {
|
|
cats.Add(bar->GetMenuLabelText(i));
|
|
}
|
|
|
|
cats.Add(COMMAND);
|
|
#endif
|
|
}
|
|
|
|
void CommandManager::GetAllCommandNames(wxArrayString &names,
|
|
bool includeMultis)
|
|
{
|
|
unsigned int i;
|
|
|
|
for(i=0; i<mCommandList.GetCount(); i++) {
|
|
if (includeMultis || !mCommandList[i]->multi)
|
|
names.Add(mCommandList[i]->name);
|
|
}
|
|
}
|
|
|
|
wxString CommandManager::GetLabelFromName(wxString name)
|
|
{
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
if (!entry)
|
|
return wxT("");
|
|
|
|
return entry->label;
|
|
}
|
|
|
|
wxString CommandManager::GetPrefixedLabelFromName(wxString name)
|
|
{
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
if (!entry)
|
|
return wxT("");
|
|
|
|
return wxString(entry->labelPrefix + wxT(" ") + entry->label).Trim(false).Trim(true);
|
|
}
|
|
|
|
wxString CommandManager::GetCategoryFromName(wxString name)
|
|
{
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
if (!entry)
|
|
return wxT("");
|
|
|
|
return entry->labelTop;
|
|
}
|
|
|
|
wxString CommandManager::GetKeyFromName(wxString name)
|
|
{
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
if (!entry)
|
|
return wxT("");
|
|
|
|
return entry->key;
|
|
}
|
|
|
|
wxString CommandManager::GetDefaultKeyFromName(wxString name)
|
|
{
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
if (!entry)
|
|
return wxT("");
|
|
|
|
return entry->defaultKey;
|
|
}
|
|
|
|
bool CommandManager::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
|
{
|
|
if (!wxStrcmp(tag, wxT("audacitykeyboard"))) {
|
|
mXMLKeysRead = 0;
|
|
}
|
|
|
|
if (!wxStrcmp(tag, wxT("command"))) {
|
|
wxString name;
|
|
wxString key;
|
|
|
|
while(*attrs) {
|
|
const wxChar *attr = *attrs++;
|
|
const wxChar *value = *attrs++;
|
|
|
|
if (!value)
|
|
break;
|
|
|
|
if (!wxStrcmp(attr, wxT("name")) && XMLValueChecker::IsGoodString(value))
|
|
name = value;
|
|
if (!wxStrcmp(attr, wxT("key")) && XMLValueChecker::IsGoodString(value))
|
|
key = KeyStringNormalize(value);
|
|
}
|
|
|
|
if (mCommandNameHash[name]) {
|
|
if (GetDefaultKeyFromName(name) != key) {
|
|
mCommandNameHash[name]->key = KeyStringNormalize(key);
|
|
mXMLKeysRead++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CommandManager::HandleXMLEndTag(const wxChar *tag)
|
|
{
|
|
if (!wxStrcmp(tag, wxT("audacitykeyboard"))) {
|
|
wxMessageBox(wxString::Format(_("Loaded %d keyboard shortcuts\n"),
|
|
mXMLKeysRead),
|
|
_("Loading keyboard shortcuts"),
|
|
wxOK | wxCENTRE);
|
|
}
|
|
}
|
|
|
|
XMLTagHandler *CommandManager::HandleXMLChild(const wxChar *tag)
|
|
{
|
|
return this;
|
|
}
|
|
|
|
void CommandManager::WriteXML(XMLWriter &xmlFile)
|
|
{
|
|
unsigned int j;
|
|
|
|
xmlFile.StartTag(wxT("audacitykeyboard"));
|
|
xmlFile.WriteAttr(wxT("audacityversion"), AUDACITY_VERSION_STRING);
|
|
|
|
for(j=0; j<mCommandList.GetCount(); j++) {
|
|
if (!mCommandList[j]->multi) {
|
|
|
|
wxString label = mCommandList[j]->label;
|
|
label = wxMenuItem::GetLabelFromText(label.BeforeFirst(wxT('\t')));
|
|
|
|
xmlFile.StartTag(wxT("command"));
|
|
xmlFile.WriteAttr(wxT("name"), mCommandList[j]->name);
|
|
xmlFile.WriteAttr(wxT("label"), label);
|
|
xmlFile.WriteAttr(wxT("key"), mCommandList[j]->key);
|
|
xmlFile.EndTag(wxT("command"));
|
|
}
|
|
}
|
|
|
|
xmlFile.EndTag(wxT("audacitykeyboard"));
|
|
}
|
|
|
|
void CommandManager::SetDefaultFlags(wxUint32 flags, wxUint32 mask)
|
|
{
|
|
mDefaultFlags = flags;
|
|
mDefaultMask = mask;
|
|
}
|
|
|
|
void CommandManager::SetCommandFlags(wxString name,
|
|
wxUint32 flags, wxUint32 mask)
|
|
{
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
if (entry) {
|
|
entry->flags = flags;
|
|
entry->mask = mask;
|
|
}
|
|
}
|
|
|
|
void CommandManager::SetCommandFlags(const wxChar **names,
|
|
wxUint32 flags, wxUint32 mask)
|
|
{
|
|
const wxChar **nptr = names;
|
|
while(*nptr) {
|
|
SetCommandFlags(wxString(*nptr), flags, mask);
|
|
nptr++;
|
|
}
|
|
}
|
|
|
|
void CommandManager::SetCommandFlags(wxUint32 flags, wxUint32 mask, ...)
|
|
{
|
|
va_list list;
|
|
va_start(list, mask);
|
|
for(;;) {
|
|
const wxChar *name = va_arg(list, const wxChar *);
|
|
if (!name)
|
|
break;
|
|
SetCommandFlags(wxString(name), flags, mask);
|
|
}
|
|
va_end(list);
|
|
}
|
|
|
|
#if defined(__WXDEBUG__)
|
|
void CommandManager::CheckDups()
|
|
{
|
|
int cnt = mCommandList.GetCount();
|
|
for (size_t j = 0; (int)j < cnt; j++) {
|
|
if (mCommandList[j]->key.IsEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
if (mCommandList[j]->label.AfterLast(wxT('\t')) == wxT("allowdup")) {
|
|
continue;
|
|
}
|
|
|
|
for (size_t i = 0; (int)i < cnt; i++) {
|
|
if (i == j) {
|
|
continue;
|
|
}
|
|
|
|
if (mCommandList[i]->key == mCommandList[j]->key) {
|
|
wxString msg;
|
|
msg.Printf(wxT("key combo '%s' assigned to '%s' and '%s'"),
|
|
mCommandList[i]->key.c_str(),
|
|
mCommandList[i]->label.BeforeFirst(wxT('\t')).c_str(),
|
|
mCommandList[j]->label.BeforeFirst(wxT('\t')).c_str());
|
|
wxASSERT_MSG(mCommandList[i]->key != mCommandList[j]->key, msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Indentation settings for Vim and Emacs and unique identifier for Arch, a
|
|
// version control system. Please do not modify past this point.
|
|
//
|
|
// Local Variables:
|
|
// c-basic-offset: 3
|
|
// indent-tabs-mode: nil
|
|
// End:
|
|
//
|
|
// vim: et sts=3 sw=3
|
|
// arch-tag: 7202d707-9bf3-4735-bdf4-a45b7e004d9a
|
|
|