1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-25 08:38:39 +02:00

Fixes for bugs 1122 and 1113

Several other issues were also fixed (hopefully ;-)).

This is a major change to accelerator handling and keyboard
capturing.  Menu shortcuts, non-menu commands, label editing,
navigation, and basically anything else were you might use
the keyboard should be thoroughly tested.
This commit is contained in:
Leland Lucius 2015-08-08 00:01:24 -05:00
parent 534741de78
commit f36fe29f96
12 changed files with 365 additions and 310 deletions

View File

@ -754,9 +754,6 @@ typedef int (AudacityApp::*SPECIALKEYEVENT)(wxKeyEvent&);
BEGIN_EVENT_TABLE(AudacityApp, wxApp) BEGIN_EVENT_TABLE(AudacityApp, wxApp)
EVT_QUERY_END_SESSION(AudacityApp::OnEndSession) EVT_QUERY_END_SESSION(AudacityApp::OnEndSession)
EVT_KEY_DOWN(AudacityApp::OnKeyDown)
EVT_CHAR(AudacityApp::OnChar)
EVT_KEY_UP(AudacityApp::OnKeyUp)
EVT_TIMER(kAudacityAppTimerID, AudacityApp::OnTimer) EVT_TIMER(kAudacityAppTimerID, AudacityApp::OnTimer)
#ifdef __WXMAC__ #ifdef __WXMAC__
EVT_MENU(wxID_NEW, AudacityApp::OnMenuNew) EVT_MENU(wxID_NEW, AudacityApp::OnMenuNew)
@ -1899,63 +1896,6 @@ void AudacityApp::OnEndSession(wxCloseEvent & event)
} }
} }
void AudacityApp::OnKeyDown(wxKeyEvent & event)
{
// Not handled
event.Skip(true);
// Make sure this event is destined for a project window
AudacityProject *prj = GetActiveProject();
// TODO: I don't know how it can happen, but it did on 2006-07-06.
// I was switching between apps fairly quickly so maybe that has something
// to do with it.
if (!prj)
return;
if (prj->HandleKeyDown(event))
event.Skip(false);
}
void AudacityApp::OnChar(wxKeyEvent & event)
{
// Not handled
event.Skip(true);
// Make sure this event is destined for a project window
AudacityProject *prj = GetActiveProject();
// TODO: I don't know how it can happen, but it did on 2006-07-06.
// I was switching between apps fairly quickly so maybe that has something
// to do with it.
if (!prj)
return;
if (prj->HandleChar(event))
event.Skip(false);
}
void AudacityApp::OnKeyUp(wxKeyEvent & event)
{
// Not handled
event.Skip(true);
// Make sure this event is destined for a project window
AudacityProject *prj = GetActiveProject();
// TODO: I don't know how it can happen, but it did on 2006-07-06.
// I was switching between apps fairly quickly so maybe that has something
// to do with it.
if (!prj)
return;
if (prj != wxGetTopLevelParent(wxWindow::FindFocus()))
return;
if (prj->HandleKeyUp(event))
event.Skip(false);
}
void AudacityApp::AddFileToHistory(const wxString & name) void AudacityApp::AddFileToHistory(const wxString & name)
{ {
mRecentFiles->AddFileToHistory(name); mRecentFiles->AddFileToHistory(name);

View File

@ -118,13 +118,6 @@ class AudacityApp:public wxApp {
void OnEndSession(wxCloseEvent & event); void OnEndSession(wxCloseEvent & event);
void OnKeyDown(wxKeyEvent & event);
void OnChar(wxKeyEvent & event);
void OnKeyUp(wxKeyEvent & event);
void OnCaptureKeyboard(wxCommandEvent & event);
void OnReleaseKeyboard(wxCommandEvent & event);
// Most Recently Used File support (for all platforms). // Most Recently Used File support (for all platforms).
void OnMRUClear(wxCommandEvent &event); void OnMRUClear(wxCommandEvent &event);
void OnMRUFile(wxCommandEvent &event); void OnMRUFile(wxCommandEvent &event);

View File

@ -1607,25 +1607,19 @@ bool LabelTrack::HandleMouse(const wxMouseEvent & evt,
// Check for keys that we will process // Check for keys that we will process
bool LabelTrack::CaptureKey(wxKeyEvent & event) bool LabelTrack::CaptureKey(wxKeyEvent & event)
{ {
// Cache the keycode // Check for modifiers and only allow shift
int keyCode = event.GetKeyCode(); int mods = event.GetModifiers();
if (mods != wxMOD_NONE && mods != wxMOD_SHIFT) {
// Check for modifiers -- this does what wxKeyEvent::HasModifiers() should
// do (it checks Control instead of CMD on Mac)
bool hasMods = ((event.GetModifiers() & (wxMOD_CMD | wxMOD_ALT)) != 0);
if (hasMods) {
return false; return false;
} }
if (mSelIndex >= 0) { if (mSelIndex >= 0) {
if (IsGoodLabelEditKey(keyCode)) { if (IsGoodLabelEditKey(event)) {
return true; return true;
} }
} }
else else {
{ if (IsGoodLabelFirstKey(event)) {
if (IsGoodLabelFirstKey(keyCode))
{
AudacityProject * pProj = GetActiveProject(); AudacityProject * pProj = GetActiveProject();
// If we're playing, don't capture if the selection is the same as the // If we're playing, don't capture if the selection is the same as the
@ -1844,7 +1838,7 @@ bool LabelTrack::OnKeyDown(SelectedRegion &newSel, wxKeyEvent & event)
break; break;
default: default:
if (!IsGoodLabelEditKey(keyCode)) { if (!IsGoodLabelEditKey(event)) {
event.Skip(); event.Skip();
} }
break; break;
@ -1888,7 +1882,7 @@ bool LabelTrack::OnKeyDown(SelectedRegion &newSel, wxKeyEvent & event)
break; break;
default: default:
if (!IsGoodLabelFirstKey(keyCode)) { if (!IsGoodLabelFirstKey(event)) {
event.Skip(); event.Skip();
} }
break; break;
@ -1908,52 +1902,17 @@ bool LabelTrack::OnChar(SelectedRegion &WXUNUSED(newSel), wxKeyEvent & event)
// Only track true changes to the label // Only track true changes to the label
bool updated = false; bool updated = false;
// Cache the keycode // Cache the character
int keyCode = event.GetKeyCode(); wxChar charCode = event.GetUnicodeKey();
wxChar charCode = keyCode; if (charCode == 0) {
#if wxUSE_UNICODE
charCode = event.GetUnicodeKey();
#endif
// We still have some filtering to do. Character events can be generated for,
// i.e., the F keys, and if they aren't handled in OnKeyDown() or in the
// command manager we get them here.
// AWD: the following behavior is not really documented (I figured it out by
// entering lots of Unicode characters on various OSes), and it's possible
// that different versions of wxWidgets act differently. It's unfortunately
// the only way I can find to allow input of full Unicode ranges without
// breaking other stuff (Audacity's command manager, keyboard menu
// navigation, Windows' Alt-+-xxxx arbitrary Unicode entry, etc.)
bool bogusChar =
#if defined(__WXMSW__) && wxUSE_UNICODE
// In Windows Unicode builds, these have keyCode not matching charCode
(keyCode != (int)charCode) ||
#else
// In Windows non-unicode, GTK+, and Mac builds the keyCode comes in the
// WXK_* range
(keyCode >= WXK_START && keyCode <= WXK_COMMAND) ||
#endif
// Avoid modified characters, but allow Alt (option) on Mac because
// several legit characters come in with it set.
(event.CmdDown()) ||
#if !defined(__WXMAC__)
(event.AltDown()) ||
#endif
// Avoid control characters on all platforms; Casting to wxUChar to avoid
// assertions in Windows non-Unicode builds...
(wxIscntrl((wxUChar)charCode));
if (bogusChar) {
event.Skip(); event.Skip();
return false; return false;
} }
// If we've reached this point and aren't currently editing, add new label // If we've reached this point and aren't currently editing, add new label
if (mSelIndex < 0) { if (mSelIndex < 0) {
// Don't create a new label for a space // Don't create a new label for a space
if (wxIsspace((wxUChar)charCode)) { if (wxIsspace(charCode)) {
event.Skip(); event.Skip();
return false; return false;
} }
@ -2799,10 +2758,9 @@ void LabelTrack::CreateCustomGlyphs()
} }
/// Returns true for keys we capture to start a label. /// Returns true for keys we capture to start a label.
bool LabelTrack::IsGoodLabelFirstKey(int keyCode) bool LabelTrack::IsGoodLabelFirstKey(const wxKeyEvent & evt)
{ {
// Allow everything before WXK_START except space, return and delete, the numpad keys int keyCode = evt.GetKeyCode();
// when numlock is on, and everything after WXK_COMMAND
return (keyCode < WXK_START return (keyCode < WXK_START
&& keyCode != WXK_SPACE && keyCode != WXK_DELETE && keyCode != WXK_RETURN) || && keyCode != WXK_SPACE && keyCode != WXK_DELETE && keyCode != WXK_RETURN) ||
(keyCode >= WXK_NUMPAD0 && keyCode <= WXK_DIVIDE) || (keyCode >= WXK_NUMPAD0 && keyCode <= WXK_DIVIDE) ||
@ -2814,8 +2772,10 @@ bool LabelTrack::IsGoodLabelFirstKey(int keyCode)
} }
/// This returns true for keys we capture for label editing. /// This returns true for keys we capture for label editing.
bool LabelTrack::IsGoodLabelEditKey(int keyCode) bool LabelTrack::IsGoodLabelEditKey(const wxKeyEvent & evt)
{ {
int keyCode = evt.GetKeyCode();
// Accept everything outside of WXK_START through WXK_COMMAND, plus the keys // Accept everything outside of WXK_START through WXK_COMMAND, plus the keys
// within that range that are usually printable, plus the ones we use for // within that range that are usually printable, plus the ones we use for
// keyboard navigation. // keyboard navigation.

View File

@ -113,8 +113,8 @@ class AUDACITY_DLL_API LabelTrack : public Track
friend class LabelStruct; friend class LabelStruct;
public: public:
bool IsGoodLabelFirstKey(int keyCode); bool IsGoodLabelFirstKey(const wxKeyEvent & evt);
bool IsGoodLabelEditKey(int keyCode); bool IsGoodLabelEditKey(const wxKeyEvent & evt);
bool IsTextSelected(); bool IsTextSelected();
void CreateCustomGlyphs(); void CreateCustomGlyphs();

View File

@ -1867,16 +1867,9 @@ void AudacityProject::OnScroll(wxScrollEvent & WXUNUSED(event))
} }
} }
// Called from the CommandMananger
bool AudacityProject::HandleKeyDown(wxKeyEvent & event) bool AudacityProject::HandleKeyDown(wxKeyEvent & event)
{ {
// Check to see if it is a meta command
if (mCommandManager.HandleMeta(event))
return true;
// Any other keypresses must be destined for this project window.
if (wxGetTopLevelParent(wxWindow::FindFocus()) != this)
return false;
if (event.GetKeyCode() == WXK_ESCAPE) if (event.GetKeyCode() == WXK_ESCAPE)
mTrackPanel->HandleEscapeKey(true); mTrackPanel->HandleEscapeKey(true);
@ -1898,20 +1891,12 @@ bool AudacityProject::HandleKeyDown(wxKeyEvent & event)
if (event.GetKeyCode() == WXK_PAGEDOWN) if (event.GetKeyCode() == WXK_PAGEDOWN)
mTrackPanel->HandlePageDownKey(); mTrackPanel->HandlePageDownKey();
return mCommandManager.HandleKey(event, GetUpdateFlags(), 0xFFFFFFFF); return true;
}
bool AudacityProject::HandleChar(wxKeyEvent & WXUNUSED(event))
{
return false;
} }
// Called from the CommandMananger
bool AudacityProject::HandleKeyUp(wxKeyEvent & event) bool AudacityProject::HandleKeyUp(wxKeyEvent & event)
{ {
// All keypresses must be destined for this project window.
if (wxGetTopLevelParent(wxWindow::FindFocus()) != this)
return false;
if (event.GetKeyCode() == WXK_ESCAPE) if (event.GetKeyCode() == WXK_ESCAPE)
mTrackPanel->HandleEscapeKey(false); mTrackPanel->HandleEscapeKey(false);
@ -1925,7 +1910,7 @@ bool AudacityProject::HandleKeyUp(wxKeyEvent & event)
if (event.GetKeyCode() == WXK_CONTROL) if (event.GetKeyCode() == WXK_CONTROL)
mTrackPanel->HandleControlKey(false); mTrackPanel->HandleControlKey(false);
return mCommandManager.HandleKey(event, GetUpdateFlags(), 0xFFFFFFFF); return true;
} }
/// Determines if flags for command are compatible with current state. /// Determines if flags for command are compatible with current state.

View File

@ -298,8 +298,8 @@ class AUDACITY_DLL_API AudacityProject: public wxFrame,
void OnODTaskUpdate(wxCommandEvent & event); void OnODTaskUpdate(wxCommandEvent & event);
void OnODTaskComplete(wxCommandEvent & event); void OnODTaskComplete(wxCommandEvent & event);
void OnTrackListUpdated(wxCommandEvent & event); void OnTrackListUpdated(wxCommandEvent & event);
bool HandleKeyDown(wxKeyEvent & event); bool HandleKeyDown(wxKeyEvent & event);
bool HandleChar(wxKeyEvent & event);
bool HandleKeyUp(wxKeyEvent & event); bool HandleKeyUp(wxKeyEvent & event);
void HandleResize(); void HandleResize();
@ -661,6 +661,9 @@ class AUDACITY_DLL_API AudacityProject: public wxFrame,
// Keyboard capture // Keyboard capture
wxWindow *mKeyboardCaptureHandler; wxWindow *mKeyboardCaptureHandler;
// CommandManager needs to use private methods
friend class CommandManager;
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
}; };

View File

@ -111,6 +111,8 @@ CommandManager. It holds the callback for one command.
#if defined(__WXMAC__) #if defined(__WXMAC__)
#include <AppKit/AppKit.h> #include <AppKit/AppKit.h>
#include <wx/osx/private.h> #include <wx/osx/private.h>
#elif defined(__WXGTK__)
#include <gtk/gtk.h>
#endif #endif
// Shared by all projects // Shared by all projects
@ -122,7 +124,7 @@ public:
{ {
#if defined(__WXMAC__) #if defined(__WXMAC__)
NSEventMask mask = NSKeyDownMask; NSEventMask mask = NSKeyDownMask;
mHandler = mHandler =
[ [
NSEvent addLocalMonitorForEventsMatchingMask:mask handler:^NSEvent *(NSEvent *event) NSEvent addLocalMonitorForEventsMatchingMask:mask handler:^NSEvent *(NSEvent *event)
@ -134,6 +136,8 @@ public:
wxWidgetImpl::FindFromWXWidget(widget); wxWidgetImpl::FindFromWXWidget(widget);
if (impl) if (impl)
{ {
mEvent = event;
wxKeyEvent wxevent(wxEVT_KEY_DOWN); wxKeyEvent wxevent(wxEVT_KEY_DOWN);
impl->SetupKeyEvent(wxevent, event); impl->SetupKeyEvent(wxevent, event);
@ -161,78 +165,84 @@ public:
int FilterEvent(wxEvent& event) int FilterEvent(wxEvent& event)
{ {
AudacityProject *project = GetActiveProject();
if (!project)
{
return Event_Skip;
}
wxEvtHandler *handler = project->GetKeyboardCaptureHandler();
if (!handler)
{
return Event_Skip;
}
wxEventType type = event.GetEventType(); wxEventType type = event.GetEventType();
if (type != wxEVT_CHAR_HOOK) if (type != wxEVT_CHAR_HOOK)
{ {
return Event_Skip; return Event_Skip;
} }
wxKeyEvent temp = (wxKeyEvent &) event; AudacityProject *project = GetActiveProject();
temp.SetEventType(wxEVT_KEY_DOWN); if (!project)
wxCommandEvent e(EVT_CAPTURE_KEY);
e.SetEventObject(&temp);
e.StopPropagation();
if (!handler->ProcessEvent(e))
{ {
return Event_Skip; return Event_Skip;
} }
wxWindow *handler = project->GetKeyboardCaptureHandler();
if (handler && HandleCapture(handler, (wxKeyEvent &) event))
{
return Event_Processed;
}
CommandManager *manager = project->GetCommandManager();
if (manager && manager->FilterKeyEvent(project, (wxKeyEvent &) event))
{
return Event_Processed;
}
return Event_Skip;
}
private:
// Returns true if the event was captured and processed
bool HandleCapture(wxWindow *target, wxKeyEvent & event)
{
if (wxGetTopLevelParent(target) != wxGetTopLevelParent(wxWindow::FindFocus()))
{
return false;
}
wxEvtHandler *handler = target->GetEventHandler();
wxKeyEvent temp = (wxKeyEvent &) event;
temp.SetEventType(wxEVT_KEY_DOWN);
#if defined(__WXGTK__)
// wxGTK uses the control and alt modifiers to represent ALTGR,
// so remove it as it might confuse the capture handlers.
if (temp.GetModifiers() == (wxMOD_CONTROL | wxMOD_ALT))
{
temp.SetControlDown(false);
temp.SetAltDown(false);
}
#endif
wxCommandEvent e(EVT_CAPTURE_KEY);
e.SetEventObject(&temp);
e.StopPropagation();
if (!handler->ProcessEvent(e))
{
return false;
}
temp.WasProcessed(); temp.WasProcessed();
temp.StopPropagation(); temp.StopPropagation();
wxEventProcessInHandlerOnly onlyDown(temp, handler); wxEventProcessInHandlerOnly onlyDown(temp, handler);
if (!handler->ProcessEvent(temp)) if (!handler->ProcessEvent(temp))
{ {
return Event_Skip; return false;
} }
int keyCode = temp.GetKeyCode(); wxString chars = GetUnicodeString(temp);
switch (keyCode) for (size_t i = 0, cnt = chars.Length(); i < cnt; i++)
{ {
case WXK_SHIFT:
case WXK_CONTROL:
case WXK_MENU:
case WXK_CAPITAL:
case WXK_NUMLOCK:
case WXK_SCROLL:
break;
default:
temp = (wxKeyEvent &) event; temp = (wxKeyEvent &) event;
temp.SetEventType(wxEVT_CHAR); temp.SetEventType(wxEVT_CHAR);
if (!temp.ShiftDown())
{
if (wxIsascii(temp.m_keyCode))
{
temp.m_keyCode = wxTolower(temp.m_keyCode);
}
if (temp.m_keyCode < WXK_START)
{
temp.m_uniChar = wxTolower(temp.m_uniChar);
}
}
temp.WasProcessed(); temp.WasProcessed();
temp.StopPropagation(); temp.StopPropagation();
temp.m_uniChar = chars[i];
wxEventProcessInHandlerOnly onlyChar(temp, handler); wxEventProcessInHandlerOnly onlyChar(temp, handler);
handler->ProcessEvent(temp); handler->ProcessEvent(temp);
break;
} }
temp = (wxKeyEvent &) event; temp = (wxKeyEvent &) event;
@ -242,13 +252,97 @@ public:
wxEventProcessInHandlerOnly onlyUp(temp, handler); wxEventProcessInHandlerOnly onlyUp(temp, handler);
handler->ProcessEvent(temp); handler->ProcessEvent(temp);
return Event_Processed; return true;
}
wxString GetUnicodeString(const wxKeyEvent & event)
{
wxString chars;
#if defined(__WXMSW__)
BYTE ks[256];
GetKeyboardState(ks);
WCHAR ucode[256];
int res = ToUnicode(event.GetRawKeyCode(), 0, ks, ucode, 256, 0);
if (res >= 1)
{
chars.Append(ucode, res);
}
#elif defined(__WXGTK__)
chars.Append((wxChar) gdk_keyval_to_unicode(event.GetRawKeyCode()));
#elif defined(__WXMAC__)
NSString *c = [mEvent charactersIgnoringModifiers];
if ([c length] == 1)
{
unichar codepoint = [c characterAtIndex:0];
if ((codepoint >= 0xF700 && codepoint <= 0xF8FF) || codepoint == 0x7F)
{
return chars;
}
}
c = [mEvent characters];
chars = [c UTF8String];
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
CFRelease(currentKeyboard);
if (uchr == NULL)
{
return chars;
}
const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr);
if (keyboardLayout == NULL)
{
return chars;
}
const UniCharCount maxStringLength = 255;
UniCharCount actualStringLength = 0;
UniChar unicodeString[maxStringLength];
UInt32 nsflags = [mEvent modifierFlags];
UInt16 modifiers = (nsflags & NSAlphaShiftKeyMask ? alphaLock : 0) |
(nsflags & NSShiftKeyMask ? shiftKey : 0) |
(nsflags & NSControlKeyMask ? controlKey : 0) |
(nsflags & NSAlternateKeyMask ? optionKey : 0) |
(nsflags & NSCommandKeyMask ? cmdKey : 0);
OSStatus status = UCKeyTranslate(keyboardLayout,
[mEvent keyCode],
kUCKeyActionDown,
(modifiers >> 8) & 0xff,
LMGetKbdType(),
0,
&mDeadKeyState,
maxStringLength,
&actualStringLength,
unicodeString);
if (status != noErr)
{
return chars;
}
chars = [[NSString stringWithCharacters:unicodeString
length:(NSInteger)actualStringLength] UTF8String];
#endif
return chars;
} }
private: private:
#if defined(__WXMAC__) #if defined(__WXMAC__)
id mHandler; id mHandler;
NSEvent *mEvent;
UInt32 mDeadKeyState;
#endif #endif
} monitor; } monitor;
@ -336,7 +430,7 @@ wxMenuBar *CommandManager::AddMenuBar(wxString sMenu)
/// ///
/// Retrieves the menubar based on the name given in AddMenuBar(name) /// Retrieves the menubar based on the name given in AddMenuBar(name)
/// ///
wxMenuBar * CommandManager::GetMenuBar(wxString sMenu) wxMenuBar * CommandManager::GetMenuBar(wxString sMenu) const
{ {
for(unsigned int i = 0; i < mMenuBarList.GetCount(); i++) for(unsigned int i = 0; i < mMenuBarList.GetCount(); i++)
{ {
@ -351,7 +445,7 @@ wxMenuBar * CommandManager::GetMenuBar(wxString sMenu)
/// ///
/// Retrieve the 'current' menubar; either NULL or the /// Retrieve the 'current' menubar; either NULL or the
/// last on in the mMenuBarList. /// last on in the mMenuBarList.
wxMenuBar * CommandManager::CurrentMenuBar() wxMenuBar * CommandManager::CurrentMenuBar() const
{ {
if(mMenuBarList.IsEmpty()) if(mMenuBarList.IsEmpty())
return NULL; return NULL;
@ -433,7 +527,7 @@ void CommandManager::EndSubMenu()
/// ///
/// This returns the 'Current' Submenu, which is the one at the /// This returns the 'Current' Submenu, which is the one at the
/// end of the mSubMenuList (or NULL, if it doesn't exist). /// end of the mSubMenuList (or NULL, if it doesn't exist).
wxMenu * CommandManager::CurrentSubMenu() wxMenu * CommandManager::CurrentSubMenu() const
{ {
if(mSubMenuList.IsEmpty()) if(mSubMenuList.IsEmpty())
return NULL; return NULL;
@ -445,7 +539,7 @@ wxMenu * CommandManager::CurrentSubMenu()
/// This returns the current menu that we're appending to - note that /// 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 /// it could be a submenu if BeginSubMenu was called and we haven't
/// reached EndSubMenu yet. /// reached EndSubMenu yet.
wxMenu * CommandManager::CurrentMenu() wxMenu * CommandManager::CurrentMenu() const
{ {
if(!mCurrentMenu) if(!mCurrentMenu)
return NULL; return NULL;
@ -515,7 +609,9 @@ void CommandManager::InsertItem(wxString name, wxString label_in,
} }
} }
int ID = NewIdentifier(name, label, menu, callback, false, 0, 0); CommandListEntry *entry = NewIdentifier(name, label, menu, callback, false, 0, 0);
int ID = entry->id;
label = GetLabel(entry);
if (checkmark >= 0) { if (checkmark >= 0) {
menu->InsertCheckItem(pos, ID, label); menu->InsertCheckItem(pos, ID, label);
@ -528,6 +624,8 @@ void CommandManager::InsertItem(wxString name, wxString label_in,
mbSeparatorAllowed = true; mbSeparatorAllowed = true;
} }
void CommandManager::AddCheck(const wxChar *name, void CommandManager::AddCheck(const wxChar *name,
const wxChar *label, const wxChar *label,
CommandFunctor *callback, CommandFunctor *callback,
@ -563,16 +661,15 @@ void CommandManager::AddItem(const wxChar *name,
unsigned int mask, unsigned int mask,
int checkmark) int checkmark)
{ {
wxString label(label_in); CommandListEntry *entry = NewIdentifier(name, label_in, accel, CurrentMenu(), callback, false, 0, 0);
label += wxT("\t"); int ID = entry->id;
label += accel ? accel : wxEmptyString; wxString label = GetLabel(entry);
int ID = NewIdentifier(name, label, CurrentMenu(), callback, false, 0, 0);
if (flags != NoFlagsSpecifed || mask != NoFlagsSpecifed) { if (flags != NoFlagsSpecifed || mask != NoFlagsSpecifed) {
SetCommandFlags(name, flags, mask); SetCommandFlags(name, flags, mask);
} }
if (checkmark >= 0) { if (checkmark >= 0) {
CurrentMenu()->AppendCheckItem(ID, label); CurrentMenu()->AppendCheckItem(ID, label);
CurrentMenu()->Check(ID, checkmark != 0); CurrentMenu()->Check(ID, checkmark != 0);
@ -593,17 +690,15 @@ void CommandManager::AddItem(const wxChar *name,
void CommandManager::AddItemList(wxString name, wxArrayString labels, void CommandManager::AddItemList(wxString name, wxArrayString labels,
CommandFunctor *callback) CommandFunctor *callback)
{ {
unsigned int i; for (size_t i = 0, cnt = labels.GetCount(); i < cnt; i++) {
CommandListEntry *entry = NewIdentifier(name,
unsigned int effLen = labels.GetCount(); labels[i],
CurrentMenu(),
wxString label; callback,
int tmpmax; true,
i,
for(i=0; i<effLen; i++) { cnt);
int ID = NewIdentifier(name, labels[i], CurrentMenu(), callback, CurrentMenu()->Append(entry->id, GetLabel(entry));
true, i, effLen);
CurrentMenu()->Append(ID, labels[i]);
mbSeparatorAllowed = true; mbSeparatorAllowed = true;
} }
} }
@ -627,11 +722,7 @@ void CommandManager::AddCommand(const wxChar *name,
unsigned int flags, unsigned int flags,
unsigned int mask) unsigned int mask)
{ {
wxString label(label_in); NewIdentifier(name, label_in, accel, NULL, callback, false, 0, 0);
label += wxT("\t");
label += accel;
NewIdentifier(name, label, NULL, callback, false, 0, 0);
if (flags != NoFlagsSpecifed || mask != NoFlagsSpecifed) { if (flags != NoFlagsSpecifed || mask != NoFlagsSpecifed) {
SetCommandFlags(name, flags, mask); SetCommandFlags(name, flags, mask);
@ -643,13 +734,8 @@ void CommandManager::AddMetaCommand(const wxChar *name,
CommandFunctor *callback, CommandFunctor *callback,
const wxChar *accel) const wxChar *accel)
{ {
wxString label(label_in); CommandListEntry *entry = NewIdentifier(name, label_in, accel, NULL, callback, false, 0, 0);
label += wxT("\t");
label += accel;
NewIdentifier(name, label, NULL, callback, false, 0, 0);
CommandListEntry *entry = mCommandNameHash[name];
entry->enabled = false; entry->enabled = false;
entry->isMeta = true; entry->isMeta = true;
entry->flags = 0; entry->flags = 0;
@ -679,15 +765,34 @@ int CommandManager::NextIdentifier(int ID)
///WARNING: Does this conflict with the identifiers set for controls/windows? ///WARNING: Does this conflict with the identifiers set for controls/windows?
///If it does, a workaround may be to keep controls below wxID_LOWEST ///If it does, a workaround may be to keep controls below wxID_LOWEST
///and keep menus above wxID_HIGHEST ///and keep menus above wxID_HIGHEST
int CommandManager::NewIdentifier(wxString name, CommandListEntry *CommandManager::NewIdentifier(const wxString & name,
wxString label, const wxString & label,
wxMenu *menu, wxMenu *menu,
CommandFunctor *callback, CommandFunctor *callback,
bool multi, bool multi,
int index, int index,
int count) int count)
{ {
CommandListEntry *tmpEntry = new CommandListEntry; return NewIdentifier(name,
label.BeforeFirst(wxT('\t')),
label.AfterFirst(wxT('\t')),
menu,
callback,
multi,
index,
count);
}
CommandListEntry *CommandManager::NewIdentifier(const wxString & name,
const wxString & label,
const wxString & accel,
wxMenu *menu,
CommandFunctor *callback,
bool multi,
int index,
int count)
{
CommandListEntry *entry = new CommandListEntry;
wxString labelPrefix; wxString labelPrefix;
if (!mSubMenuList.IsEmpty()) { if (!mSubMenuList.IsEmpty()) {
@ -704,86 +809,88 @@ int CommandManager::NewIdentifier(wxString name,
// menu (which might be translated). // menu (which might be translated).
mCurrentID = NextIdentifier(mCurrentID); mCurrentID = NextIdentifier(mCurrentID);
tmpEntry->id = mCurrentID; entry->id = mCurrentID;
tmpEntry->key = GetKey(label);
#if defined(__WXMAC__) #if defined(__WXMAC__)
if (name == wxT("Preferences")) if (name == wxT("Preferences"))
tmpEntry->id = wxID_PREFERENCES; entry->id = wxID_PREFERENCES;
else if (name == wxT("Exit")) else if (name == wxT("Exit"))
tmpEntry->id = wxID_EXIT; entry->id = wxID_EXIT;
else if (name == wxT("About")) else if (name == wxT("About"))
tmpEntry->id = wxID_ABOUT; entry->id = wxID_ABOUT;
#endif #endif
tmpEntry->defaultKey = tmpEntry->key; entry->defaultKey = entry->key;
entry->name = name;
entry->label = label;
entry->key = KeyStringNormalize(accel.BeforeFirst(wxT('\t')));
entry->labelPrefix = labelPrefix;
entry->labelTop = wxMenuItem::GetLabelText(mCurrentMenuName);
entry->menu = menu;
entry->callback = callback;
entry->multi = multi;
entry->index = index;
entry->count = count;
entry->flags = mDefaultFlags;
entry->mask = mDefaultMask;
entry->enabled = true;
entry->wantevent = (accel.Find(wxT("\twantevent")) != wxNOT_FOUND);
entry->ignoredown = (accel.Find(wxT("\tignoredown")) != wxNOT_FOUND);
entry->isMeta = false;
// For key bindings for commands with a list, such as effects, // For key bindings for commands with a list, such as effects,
// the name in prefs is the category name plus the effect name. // the name in prefs is the category name plus the effect name.
if( multi ) if (multi) {
name= wxString::Format( wxT("%s:%s"), name.c_str(), label.c_str() ); entry->name = wxString::Format( wxT("%s:%s"), name.c_str(), label.c_str() );
tmpEntry->name = name; }
tmpEntry->label = label;
tmpEntry->labelPrefix = labelPrefix;
tmpEntry->labelTop = wxMenuItem::GetLabelText(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;
tmpEntry->wantevent = (label.Find(wxT("\twantevent")) != wxNOT_FOUND);
tmpEntry->ignoredown = (label.Find(wxT("\tignoredown")) != wxNOT_FOUND);
tmpEntry->isMeta = false;
// Key from preferences overridse the default key given // Key from preferences overridse the default key given
gPrefs->SetPath(wxT("/NewKeys")); gPrefs->SetPath(wxT("/NewKeys"));
if (gPrefs->HasEntry(name)) { if (gPrefs->HasEntry(name)) {
tmpEntry->key = KeyStringNormalize(gPrefs->Read(name, tmpEntry->key)); entry->key = KeyStringNormalize(gPrefs->Read(name, entry->key));
} }
gPrefs->SetPath(wxT("/")); gPrefs->SetPath(wxT("/"));
mCommandList.Add(tmpEntry); mCommandList.Add(entry);
mCommandIDHash[tmpEntry->id] = tmpEntry; mCommandIDHash[entry->id] = entry;
#if defined(__WXDEBUG__) #if defined(__WXDEBUG__)
CommandListEntry *prev = mCommandNameHash[name]; CommandListEntry *prev = mCommandNameHash[entry->name];
if (prev) { if (prev) {
// Under Linux it looks as if we may ask for a newID for the same command // Under Linux it looks as if we may ask for a newID for the same command
// more than once. So it's only an error if two different commands // more than once. So it's only an error if two different commands
// have the exact same name. // have the exact same name.
if( prev->label != tmpEntry->label ) if( prev->label != entry->label )
{ {
wxLogDebug(wxT("Command '%s' defined by '%s' and '%s'"), wxLogDebug(wxT("Command '%s' defined by '%s' and '%s'"),
name.c_str(), entry->name.c_str(),
prev->label.BeforeFirst(wxT('\t')).c_str(), prev->label.BeforeFirst(wxT('\t')).c_str(),
tmpEntry->label.BeforeFirst(wxT('\t')).c_str()); entry->label.BeforeFirst(wxT('\t')).c_str());
wxFAIL_MSG( wxString::Format(wxT("Command '%s' defined by '%s' and '%s'"), wxFAIL_MSG(wxString::Format(wxT("Command '%s' defined by '%s' and '%s'"),
name.c_str(), entry->name.c_str(),
prev->label.BeforeFirst(wxT('\t')).c_str(), prev->label.BeforeFirst(wxT('\t')).c_str(),
tmpEntry->label.BeforeFirst(wxT('\t')).c_str())); entry->label.BeforeFirst(wxT('\t')).c_str()));
} }
} }
#endif #endif
mCommandNameHash[name] = tmpEntry; mCommandNameHash[name] = entry;
if (tmpEntry->key != wxT("")) { if (entry->key != wxT("")) {
mCommandKeyHash[tmpEntry->key] = tmpEntry; mCommandKeyHash[entry->key] = entry;
} }
return tmpEntry->id; return entry;
} }
wxString CommandManager::GetKey(wxString label) wxString CommandManager::GetLabel(const CommandListEntry *entry) const
{ {
wxString key = label.AfterFirst(wxT('\t')).BeforeFirst(wxT('\t')); wxString label = entry->label;
if (key.IsEmpty()) { if (!entry->key.IsEmpty())
return key; {
label += wxT("\t") + entry->key;
} }
return KeyStringNormalize(key); return label;
} }
///Enables or disables a menu item based on its name (not the ///Enables or disables a menu item based on its name (not the
@ -932,6 +1039,44 @@ void CommandManager::TellUserWhyDisallowed( wxUint32 flagsGot, wxUint32 flagsReq
wxMessageBox(reason, _("Disallowed"), wxICON_WARNING | wxOK ); wxMessageBox(reason, _("Disallowed"), wxICON_WARNING | wxOK );
} }
///
///
///
bool CommandManager::FilterKeyEvent(AudacityProject *project, wxKeyEvent & evt)
{
if (HandleMeta(evt))
{
return true;
}
// Any other keypresses must be destined for this project window.
if (wxGetTopLevelParent(wxWindow::FindFocus()) != project)
{
return false;
}
wxUint32 flags = project->GetUpdateFlags();
wxKeyEvent temp = evt;
temp.SetEventType(wxEVT_KEY_DOWN);
if (HandleKey(temp, flags, 0xFFFFFFFF))
{
temp.SetEventType(wxEVT_KEY_UP);
HandleKey(temp, flags, 0xFFFFFFFF);
return true;
}
if (project->HandleKeyDown(evt))
{
wxKeyEvent temp = evt;
temp.SetEventType(wxEVT_KEY_UP);
project->HandleKeyUp(temp);
}
return false;
}
/// HandleCommandEntry() takes a CommandListEntry and executes it /// HandleCommandEntry() takes a CommandListEntry and executes it
/// returning true iff successful. If you pass any flags, /// returning true iff successful. If you pass any flags,
///the command won't be executed unless the flags are compatible ///the command won't be executed unless the flags are compatible
@ -952,7 +1097,6 @@ bool CommandManager::HandleCommandEntry(CommandListEntry * entry, wxUint32 flags
// NB: The call may have the side effect of changing flags. // NB: The call may have the side effect of changing flags.
bool allowed = proj->TryToMakeActionAllowed( flags, entry->flags, combinedMask ); bool allowed = proj->TryToMakeActionAllowed( flags, entry->flags, combinedMask );
if (!allowed) if (!allowed)
{ {
TellUserWhyDisallowed( TellUserWhyDisallowed(

View File

@ -71,6 +71,8 @@ WX_DEFINE_USER_EXPORTED_ARRAY(CommandListEntry *, CommandList, class AUDACITY_DL
WX_DECLARE_STRING_HASH_MAP_WITH_DECL(CommandListEntry *, CommandNameHash, class AUDACITY_DLL_API); WX_DECLARE_STRING_HASH_MAP_WITH_DECL(CommandListEntry *, CommandNameHash, class AUDACITY_DLL_API);
WX_DECLARE_HASH_MAP_WITH_DECL(int, CommandListEntry *, wxIntegerHash, wxIntegerEqual, CommandIDHash, class AUDACITY_DLL_API); WX_DECLARE_HASH_MAP_WITH_DECL(int, CommandListEntry *, wxIntegerHash, wxIntegerEqual, CommandIDHash, class AUDACITY_DLL_API);
class AudacityProject;
class AUDACITY_DLL_API CommandManager: public XMLTagHandler class AUDACITY_DLL_API CommandManager: public XMLTagHandler
{ {
public: public:
@ -183,6 +185,7 @@ class AUDACITY_DLL_API CommandManager: public XMLTagHandler
// //
// Executing commands // Executing commands
// //
bool FilterKeyEvent(AudacityProject *project, wxKeyEvent & evt);
bool HandleCommandEntry(CommandListEntry * entry, wxUint32 flags, wxUint32 mask, const wxEvent * evt = NULL); bool HandleCommandEntry(CommandListEntry * entry, wxUint32 flags, wxUint32 mask, const wxEvent * evt = NULL);
bool HandleMenuID(int id, wxUint32 flags, wxUint32 mask); bool HandleMenuID(int id, wxUint32 flags, wxUint32 mask);
bool HandleKey(wxKeyEvent &evt, wxUint32 flags, wxUint32 mask); bool HandleKey(wxKeyEvent &evt, wxUint32 flags, wxUint32 mask);
@ -228,18 +231,30 @@ class AUDACITY_DLL_API CommandManager: public XMLTagHandler
protected: protected:
wxMenuBar * CurrentMenuBar(); wxMenuBar * CurrentMenuBar() const;
wxMenuBar * GetMenuBar(wxString sMenu); wxMenuBar * GetMenuBar(wxString sMenu) const;
wxMenu * CurrentSubMenu(); wxMenu * CurrentSubMenu() const;
wxMenu * CurrentMenu(); wxMenu * CurrentMenu() const;
int NextIdentifier(int ID); int NextIdentifier(int ID);
int NewIdentifier(wxString name, wxString label, wxMenu *menu, CommandListEntry *NewIdentifier(const wxString & name,
CommandFunctor *callback, const wxString & label,
bool multi, int index, int count); wxMenu *menu,
CommandFunctor *callback,
bool multi,
int index,
int count);
CommandListEntry *NewIdentifier(const wxString & name,
const wxString & label,
const wxString & accel,
wxMenu *menu,
CommandFunctor *callback,
bool multi,
int index,
int count);
void Enable(CommandListEntry *entry, bool enabled); void Enable(CommandListEntry *entry, bool enabled);
wxString GetKey(wxString label); wxString GetLabel(const CommandListEntry *entry) const;
private: private:
MenuBarList mMenuBarList; MenuBarList mMenuBarList;

View File

@ -62,11 +62,8 @@ wxString KeyEventToKeyString(const wxKeyEvent & event)
long key = event.GetKeyCode(); long key = event.GetKeyCode();
if (event.ControlDown()) if (event.ControlDown())
#if defined(__WXMAC__)
newStr += wxT("XCtrl+");
#else
newStr += wxT("Ctrl+"); newStr += wxT("Ctrl+");
#endif
if (event.AltDown()) if (event.AltDown())
newStr += wxT("Alt+"); newStr += wxT("Alt+");
@ -74,11 +71,11 @@ wxString KeyEventToKeyString(const wxKeyEvent & event)
newStr += wxT("Shift+"); newStr += wxT("Shift+");
#if defined(__WXMAC__) #if defined(__WXMAC__)
if (event.MetaDown()) if (event.RawControlDown())
newStr += wxT("Ctrl+"); newStr += wxT("XCtrl+");
#endif #endif
if (event.ControlDown() && key >= 1 && key <= 26) if (event.RawControlDown() && key >= 1 && key <= 26)
newStr += (wxChar)(64 + key); newStr += (wxChar)(64 + key);
else if (key >= 33 && key <= 126) else if (key >= 33 && key <= 126)
newStr += (wxChar)key; newStr += (wxChar)key;

View File

@ -305,7 +305,10 @@ void TranscriptionToolBar::RegenerateTooltips()
{ {
#if wxUSE_TOOLTIPS #if wxUSE_TOOLTIPS
mButtons[TTB_PlaySpeed]->SetToolTip(_("Play-at-speed")); mButtons[TTB_PlaySpeed]->SetToolTip(_("Play-at-speed"));
mPlaySpeedSlider->SetToolTip(_("Playback Speed"));
wxString tip;
tip.Printf(_("Playback Speed") + wxT(": %.2fx"), mPlaySpeedSlider->Get());
mPlaySpeedSlider->SetToolTip(tip);
#ifdef EXPERIMENTAL_VOICE_DETECTION #ifdef EXPERIMENTAL_VOICE_DETECTION
mButtons[TTB_StartOn]->SetToolTip(TRANSLATABLE("Left-to-On")); mButtons[TTB_StartOn]->SetToolTip(TRANSLATABLE("Left-to-On"));
@ -487,6 +490,7 @@ void TranscriptionToolBar::OnPlaySpeed(wxCommandEvent & WXUNUSED(event))
void TranscriptionToolBar::OnSpeedSlider(wxCommandEvent& WXUNUSED(event)) void TranscriptionToolBar::OnSpeedSlider(wxCommandEvent& WXUNUSED(event))
{ {
mPlaySpeed = (mPlaySpeedSlider->Get()) * 100; mPlaySpeed = (mPlaySpeedSlider->Get()) * 100;
RegenerateTooltips();
// If IO is busy, abort immediately // If IO is busy, abort immediately
// AWD: This is disabled to work around a hang on Linux when PulseAudio is // AWD: This is disabled to work around a hang on Linux when PulseAudio is

View File

@ -465,6 +465,12 @@ void LWSlider::SetScroll(float line, float page)
void LWSlider::CreatePopWin() void LWSlider::CreatePopWin()
{ {
if (mTipPanel)
{
delete mTipPanel;
mTipPanel = NULL;
}
wxString maxTipLabel = mName + wxT(": 000000"); wxString maxTipLabel = mName + wxT(": 000000");
if (mStyle == PAN_SLIDER || mStyle == DB_SLIDER || mStyle == SPEED_SLIDER if (mStyle == PAN_SLIDER || mStyle == DB_SLIDER || mStyle == SPEED_SLIDER
@ -912,6 +918,15 @@ void LWSlider::OnMouseEvent(wxMouseEvent & event)
if (!mEnabled) if (!mEnabled)
return; return;
// Windows sends a right button mouse event when you press the context menu
// key, so ignore it.
if ((event.RightDown() && !event.RightIsDown()) ||
(event.RightUp() && event.GetPosition() == wxPoint(-1, -1)))
{
event.Skip(false);
return;
}
float prevValue = mCurrentValue; float prevValue = mCurrentValue;
// Figure out the thumb position // Figure out the thumb position
@ -940,7 +955,7 @@ void LWSlider::OnMouseEvent(wxMouseEvent & event)
} }
else if( event.ButtonDown() ) else if( event.ButtonDown() )
{ {
if( mDefaultShortcut && event.CmdDown() ) if( mDefaultShortcut && event.ControlDown() )
{ {
mCurrentValue = mDefaultValue; mCurrentValue = mDefaultValue;
} }
@ -974,8 +989,6 @@ void LWSlider::OnMouseEvent(wxMouseEvent & event)
mParent->CaptureMouse(); mParent->CaptureMouse();
} }
wxASSERT(mTipPanel == NULL);
CreatePopWin(); CreatePopWin();
FormatPopWin(); FormatPopWin();
SetPopWinPosition(); SetPopWinPosition();
@ -990,10 +1003,11 @@ void LWSlider::OnMouseEvent(wxMouseEvent & event)
if (mParent->HasCapture()) if (mParent->HasCapture())
mParent->ReleaseMouse(); mParent->ReleaseMouse();
wxASSERT(mTipPanel != NULL); if (mTipPanel)
{
delete mTipPanel; delete mTipPanel;
mTipPanel = NULL; mTipPanel = NULL;
}
//restore normal tooltip behavor for mouseovers //restore normal tooltip behavor for mouseovers
wxToolTip::Enable(true); wxToolTip::Enable(true);

View File

@ -2177,14 +2177,14 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions()); AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions());
options.playLooped = (loopEnabled && evt.ShiftDown()); options.playLooped = (loopEnabled && evt.ShiftDown());
if (!evt.CmdDown()) // Use CmdDown rather than ControlDown. See bug 746 if (!evt.ControlDown())
options.pStartTime = &mPlayRegionStart; options.pStartTime = &mPlayRegionStart;
else else
options.timeTrack = NULL; options.timeTrack = NULL;
ctb->PlayPlayRegion((SelectedRegion(start, end)), ctb->PlayPlayRegion((SelectedRegion(start, end)),
options, options,
evt.CmdDown(), evt.ControlDown(),
false, false,
true); true);