From ef933c3649f69416811fc76226b39e4c97f6db72 Mon Sep 17 00:00:00 2001 From: casey langen Date: Mon, 11 Sep 2017 17:23:12 -0700 Subject: [PATCH] Added the ability to overwrite UP/DOWN/LEFT/PAGE_UP/PAGE_DOWN/HOME/END keys via `hotkeys.json` to allow for vim-like bindings. --- src/musikcube/Main.cpp | 1 + src/musikcube/app/layout/LibraryLayout.cpp | 15 +---- src/musikcube/app/layout/MainLayout.cpp | 6 +- src/musikcube/app/layout/SearchLayout.cpp | 5 +- .../app/layout/TrackSearchLayout.cpp | 6 +- src/musikcube/app/util/Hotkeys.cpp | 49 ++++++++++++++ src/musikcube/app/util/Hotkeys.h | 13 +++- src/musikcube/app/window/TransportWindow.cpp | 5 +- src/musikcube/cursespp/INavigationKeys.h | 66 +++++++++++++++++++ src/musikcube/cursespp/LayoutBase.cpp | 5 +- src/musikcube/cursespp/ScrollableWindow.cpp | 13 ++-- src/musikcube/cursespp/ShortcutsWindow.cpp | 6 +- src/musikcube/cursespp/Window.cpp | 42 ++++++++++++ src/musikcube/cursespp/Window.h | 5 +- src/musikcube/musikcube.vcxproj | 1 + src/musikcube/musikcube.vcxproj.filters | 3 + 16 files changed, 207 insertions(+), 34 deletions(-) create mode 100644 src/musikcube/cursespp/INavigationKeys.h diff --git a/src/musikcube/Main.cpp b/src/musikcube/Main.cpp index 360617b4e..28493deb3 100644 --- a/src/musikcube/Main.cpp +++ b/src/musikcube/Main.cpp @@ -135,6 +135,7 @@ int main(int argc, char* argv[]) { PlaybackService playback(Window::MessageQueue(), library, transport); GlobalHotkeys globalHotkeys(playback, library); + Window::SetNavigationKeys(Hotkeys::NavigationKeys()); musik::core::plugin::InstallDependencies(library); diff --git a/src/musikcube/app/layout/LibraryLayout.cpp b/src/musikcube/app/layout/LibraryLayout.cpp index 4a690d6bb..801c28aa8 100755 --- a/src/musikcube/app/layout/LibraryLayout.cpp +++ b/src/musikcube/app/layout/LibraryLayout.cpp @@ -327,16 +327,7 @@ void LibraryLayout::ProcessMessage(musik::core::runtime::IMessage &message) { } bool LibraryLayout::KeyPress(const std::string& key) { - if (key == "^[") { /* switches between browse/now playing */ - if (this->visibleLayout != this->browseLayout) { - this->ShowBrowse(); - } - else { - this->ShowNowPlaying(); - } - return true; - } - else if (Hotkeys::Is(Hotkeys::NavigateLibraryPlayQueue, key)) { + if (Hotkeys::Is(Hotkeys::NavigateLibraryPlayQueue, key)) { this->ShowNowPlaying(); return true; } @@ -352,12 +343,12 @@ bool LibraryLayout::KeyPress(const std::string& key) { this->ShowTrackSearch(); return true; } - else if (this->GetFocus() == this->transportView && key == "KEY_UP") { + else if (this->GetFocus() == this->transportView && Hotkeys::Is(Hotkeys::Up, key)) { this->transportView->Blur(); this->visibleLayout->FocusLast(); return true; } - else if (this->GetFocus() == this->transportView && key == "KEY_DOWN") { + else if (this->GetFocus() == this->transportView && Hotkeys::Is(Hotkeys::Down, key)) { this->transportView->Blur(); this->visibleLayout->FocusFirst(); return true; diff --git a/src/musikcube/app/layout/MainLayout.cpp b/src/musikcube/app/layout/MainLayout.cpp index eca83d066..b8139ca51 100755 --- a/src/musikcube/app/layout/MainLayout.cpp +++ b/src/musikcube/app/layout/MainLayout.cpp @@ -300,7 +300,7 @@ bool MainLayout::KeyPress(const std::string& key) { shortcut bar focus... */ if (key == "^[" || (key == "KEY_ENTER" && this->shortcutsFocused) || - (key == "KEY_UP" && this->shortcutsFocused)) + (Hotkeys::Is(Hotkeys::Up, key) && this->shortcutsFocused)) { this->shortcutsFocused = !this->shortcutsFocused; @@ -315,8 +315,8 @@ bool MainLayout::KeyPress(const std::string& key) { } if (this->shortcutsFocused) { - if (key == "KEY_DOWN" || key == "KEY_LEFT" || - key == "KEY_UP" || key == "KEY_RIGHT") + if (Hotkeys::Is(Hotkeys::Down, key) || Hotkeys::Is(Hotkeys::Left, key) || + Hotkeys::Is(Hotkeys::Up, key) || Hotkeys::Is(Hotkeys::Right, key)) { /* layouts allow focusing via TAB and sometimes arrow keys. suppress these from bubbling. */ diff --git a/src/musikcube/app/layout/SearchLayout.cpp b/src/musikcube/app/layout/SearchLayout.cpp index 65abfe01b..d32025a44 100755 --- a/src/musikcube/app/layout/SearchLayout.cpp +++ b/src/musikcube/app/layout/SearchLayout.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "SearchLayout.h" using namespace musik::core::library::constants; @@ -158,13 +159,13 @@ bool SearchLayout::KeyPress(const std::string& key) { } } - if (key == "KEY_DOWN") { + if (Hotkeys::Is(Hotkeys::Down, key)) { if (this->GetFocus() == this->input) { this->FocusNext(); return true; } } - else if (key == "KEY_UP") { + else if (Hotkeys::Is(Hotkeys::Up, key)) { if (IS_CATEGORY(this->GetFocus())) { this->SetFocus(this->input); return true; diff --git a/src/musikcube/app/layout/TrackSearchLayout.cpp b/src/musikcube/app/layout/TrackSearchLayout.cpp index e7ce969de..42f3248de 100755 --- a/src/musikcube/app/layout/TrackSearchLayout.cpp +++ b/src/musikcube/app/layout/TrackSearchLayout.cpp @@ -92,7 +92,7 @@ void TrackSearchLayout::InitializeWindows() { this->input->EnterPressed.connect(this, &TrackSearchLayout::OnEnterPressed); this->input->SetFocusOrder(0); this->AddWindow(this->input); - + this->trackList.reset(new TrackListView(this->playback, this->library)); this->trackList->SetFocusOrder(1); this->trackList->SetAllowArrowKeyPropagation(); @@ -144,13 +144,13 @@ void TrackSearchLayout::OnEnterPressed(cursespp::TextInput* sender) { } bool TrackSearchLayout::KeyPress(const std::string& key) { - if (key == "KEY_DOWN") { + if (Hotkeys::Is(Hotkeys::Down, key)) { if (this->GetFocus() == this->input) { this->FocusNext(); return true; } } - else if (key == "KEY_UP") { + else if (Hotkeys::Is(Hotkeys::Up, key)) { if (this->GetFocus() == this->trackList) { this->SetFocus(this->input); return true; diff --git a/src/musikcube/app/util/Hotkeys.cpp b/src/musikcube/app/util/Hotkeys.cpp index 233caa194..80bceece0 100755 --- a/src/musikcube/app/util/Hotkeys.cpp +++ b/src/musikcube/app/util/Hotkeys.cpp @@ -54,6 +54,15 @@ struct EnumHasher { /* map from internal ID to user-friendly JSON key name */ static std::unordered_map NAME_TO_ID = { + { "key_up", Id::Up }, + { "key_down", Id::Down }, + { "key_left", Id::Left }, + { "key_right", Id::Right }, + { "key_page_up", Id::PageUp }, + { "key_page_down", Id::PageDown }, + { "key_home", Id::Home }, + { "key_end", Id::End }, + { "navigate_library", Id::NavigateLibrary }, { "navigate_library_browse", Id::NavigateLibraryBrowse }, { "navigate_library_browse_artists", Id::NavigateLibraryBrowseArtists }, @@ -105,6 +114,15 @@ static std::unordered_map NAME_TO_ID = { /* default hotkeys */ static std::unordered_map ID_TO_DEFAULT = { + { Id::Up, "KEY_UP" }, + { Id::Down, "KEY_DOWN" }, + { Id::Left, "KEY_LEFT" }, + { Id::Right, "KEY_RIGHT" }, + { Id::PageUp, "KEY_PPAGE" }, + { Id::PageDown, "KEY_NPAGE" }, + { Id::Home, "KEY_HOME" }, + { Id::End, "KEY_END" }, + { Id::NavigateLibrary, "a" }, { Id::NavigateLibraryBrowse, "b" }, { Id::NavigateLibraryBrowseArtists, "1" }, @@ -254,3 +272,34 @@ std::string Hotkeys::Get(Id id) { return ""; } + +class NavigationKeysImpl : public cursespp::INavigationKeys { + public: + virtual bool Up(const std::string& key) override { return Up() == key; } + virtual bool Down(const std::string& key) override { return Down() == key; } + virtual bool Left(const std::string& key) override { return Left() == key; } + virtual bool Right(const std::string& key) override { return Right() == key; } + virtual bool PageUp(const std::string& key) override { return PageUp() == key; } + virtual bool PageDown(const std::string& key) override { return PageDown() == key; } + virtual bool Home(const std::string& key) override { return Home() == key; } + virtual bool End(const std::string& key) override { return End() == key; } + virtual bool Next(const std::string& key) override { return Next() == key; } + virtual bool Prev(const std::string& key) override { return Prev() == key; } + virtual bool Mode(const std::string& key) override { return Mode() == key; } + + virtual std::string Up() override { return Hotkeys::Get(Hotkeys::Up); } + virtual std::string Down() override { return Hotkeys::Get(Hotkeys::Down); } + virtual std::string Left() override { return Hotkeys::Get(Hotkeys::Left); } + virtual std::string Right() override { return Hotkeys::Get(Hotkeys::Right); } + virtual std::string PageUp() override { return Hotkeys::Get(Hotkeys::PageUp); } + virtual std::string PageDown() override { return Hotkeys::Get(Hotkeys::PageDown); } + virtual std::string Home() override { return Hotkeys::Get(Hotkeys::Home); } + virtual std::string End() override { return Hotkeys::Get(Hotkeys::End); } + virtual std::string Next() override { return "KEY_TAB"; } + virtual std::string Prev() override { return "KEY_BTAB"; } + virtual std::string Mode() override { return "^["; } +}; + +std::shared_ptr Hotkeys::NavigationKeys() { + return std::shared_ptr(new NavigationKeysImpl()); +} diff --git a/src/musikcube/app/util/Hotkeys.h b/src/musikcube/app/util/Hotkeys.h index 2367d76d4..84da06359 100755 --- a/src/musikcube/app/util/Hotkeys.h +++ b/src/musikcube/app/util/Hotkeys.h @@ -36,13 +36,23 @@ #include "stdafx.h" -#include +#include namespace musik { namespace cube { class Hotkeys { public: enum Id { + /* selection */ + Up, + Down, + Left, + Right, + PageUp, + PageDown, + Home, + End, + /* navigation */ NavigateLibrary, NavigateLibraryBrowse, @@ -100,6 +110,7 @@ namespace musik { static bool Is(Id id, const std::string& kn); static std::string Get(Id id); + static std::shared_ptr NavigationKeys(); private: Hotkeys(); diff --git a/src/musikcube/app/window/TransportWindow.cpp b/src/musikcube/app/window/TransportWindow.cpp index ac0793dc5..cd9ac0d20 100755 --- a/src/musikcube/app/window/TransportWindow.cpp +++ b/src/musikcube/app/window/TransportWindow.cpp @@ -46,6 +46,7 @@ #include #include +#include #include #include @@ -296,11 +297,11 @@ static size_t writePlayingFormat( } static inline bool inc(const std::string& kn) { - return (/*kn == "KEY_UP" ||*/ kn == "KEY_RIGHT"); + return (Hotkeys::Is(Hotkeys::Right, kn)); } static inline bool dec(const std::string& kn) { - return (/*kn == "KEY_DOWN" ||*/ kn == "KEY_LEFT"); + return (Hotkeys::Is(Hotkeys::Left, kn)); } TransportWindow::TransportWindow(musik::core::audio::PlaybackService& playback) diff --git a/src/musikcube/cursespp/INavigationKeys.h b/src/musikcube/cursespp/INavigationKeys.h new file mode 100644 index 000000000..86e4bf114 --- /dev/null +++ b/src/musikcube/cursespp/INavigationKeys.h @@ -0,0 +1,66 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2017 musikcube team +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////////// + +#pragma once + +namespace cursespp { + class INavigationKeys { + public: + virtual ~INavigationKeys() { } + + virtual bool Up(const std::string& key) = 0; + virtual bool Down(const std::string& key) = 0; + virtual bool Left(const std::string& key) = 0; + virtual bool Right(const std::string& key) = 0; + virtual bool Next(const std::string& key) = 0; + virtual bool PageUp(const std::string& key) = 0; + virtual bool PageDown(const std::string& key) = 0; + virtual bool Home(const std::string& key) = 0; + virtual bool End(const std::string& key) = 0; + virtual bool Prev(const std::string& key) = 0; + virtual bool Mode(const std::string& key) = 0; + + virtual std::string Up() = 0; + virtual std::string Down() = 0; + virtual std::string Left() = 0; + virtual std::string Right() = 0; + virtual std::string Next() = 0; + virtual std::string PageUp() = 0; + virtual std::string PageDown() = 0; + virtual std::string Home() = 0; + virtual std::string End() = 0; + virtual std::string Prev() = 0; + virtual std::string Mode() = 0; + }; +} diff --git a/src/musikcube/cursespp/LayoutBase.cpp b/src/musikcube/cursespp/LayoutBase.cpp index de805fc7b..b8768c7fe 100755 --- a/src/musikcube/cursespp/LayoutBase.cpp +++ b/src/musikcube/cursespp/LayoutBase.cpp @@ -386,11 +386,12 @@ void LayoutBase::SetFocusMode(FocusMode mode) { } bool LayoutBase::KeyPress(const std::string& key) { - if (key == "KEY_LEFT" || key == "KEY_UP") { + auto& keys = NavigationKeys(); + if (keys.Left(key) || keys.Up(key)) { this->FocusPrev(); return true; } - else if (key == "KEY_RIGHT" || key == "KEY_DOWN") { + else if (keys.Right(key) || keys.Down(key)) { this->FocusNext(); return true; } diff --git a/src/musikcube/cursespp/ScrollableWindow.cpp b/src/musikcube/cursespp/ScrollableWindow.cpp index 6885c1a54..a48675e18 100755 --- a/src/musikcube/cursespp/ScrollableWindow.cpp +++ b/src/musikcube/cursespp/ScrollableWindow.cpp @@ -121,23 +121,24 @@ bool ScrollableWindow::KeyPress(const std::string& key) { the logical (selected) index doesn't actually change -- i.e. the user is at the beginning or end of the scrollable area. this is so controllers can change focus in response to UP/DOWN if necessary. */ + auto& keys = NavigationKeys(); - if (key == "KEY_NPAGE") { this->PageDown(); return true; } - else if (key == "KEY_PPAGE") { this->PageUp(); return true; } - else if (key == "KEY_DOWN") { + if (keys.PageDown(key)) { this->PageDown(); return true; } + else if (keys.PageUp(key)) { this->PageUp(); return true; } + else if (keys.Down(key)) { const size_t before = this->GetScrollPosition().logicalIndex; this->ScrollDown(); const size_t after = this->GetScrollPosition().logicalIndex; return !this->allowArrowKeyPropagation || (before != after); } - else if (key == "KEY_UP") { + else if (keys.Up(key)) { const size_t before = this->GetScrollPosition().logicalIndex; this->ScrollUp(); const size_t after = this->GetScrollPosition().logicalIndex; return !this->allowArrowKeyPropagation || (before != after); } - else if (key == "KEY_HOME") { this->ScrollToTop(); return true; } - else if (key == "KEY_END") { this->ScrollToBottom(); return true; } + else if (keys.Home(key)) { this->ScrollToTop(); return true; } + else if (keys.End(key)) { this->ScrollToBottom(); return true; } return false; } diff --git a/src/musikcube/cursespp/ShortcutsWindow.cpp b/src/musikcube/cursespp/ShortcutsWindow.cpp index 5a199a85b..f5e26466c 100755 --- a/src/musikcube/cursespp/ShortcutsWindow.cpp +++ b/src/musikcube/cursespp/ShortcutsWindow.cpp @@ -109,7 +109,9 @@ bool ShortcutsWindow::KeyPress(const std::string& key) { if (this->changedCallback && this->IsFocused()) { int count = (int) this->entries.size(); if (count > 0) { - if (key == "KEY_RIGHT") { + auto& keys = NavigationKeys(); + + if (keys.Right(key)) { int active = getActiveIndex(); if (active >= 0 && active + 1 < count) { this->activeKey = this->entries[active + 1]->key; @@ -120,7 +122,7 @@ bool ShortcutsWindow::KeyPress(const std::string& key) { this->Redraw(); return true; } - else if (key == "KEY_LEFT") { + else if (keys.Left(key)) { int active = getActiveIndex(); if (active > 0) { this->activeKey = this->entries[active - 1]->key; diff --git a/src/musikcube/cursespp/Window.cpp b/src/musikcube/cursespp/Window.cpp index a5f899b2a..8614c23a2 100755 --- a/src/musikcube/cursespp/Window.cpp +++ b/src/musikcube/cursespp/Window.cpp @@ -53,6 +53,7 @@ static bool freeze = false; static Window* top = nullptr; static MessageQueue messageQueue; +static std::shared_ptr keys; #define ENABLE_BOUNDS_CHECK 1 @@ -782,3 +783,44 @@ void Window::Blur() { this->Redraw(); } } + +void Window::SetNavigationKeys(std::shared_ptr keys) { + ::keys = keys; +} + +/* default keys for navigating around sub-views. apps can override this shim to +provide VIM-like keybindings, if it wants... */ +static class DefaultNavigationKeys : public INavigationKeys { + public: + virtual bool Up(const std::string& key) override { return Up() == key; } + virtual bool Down(const std::string& key) override { return Down() == key; } + virtual bool Left(const std::string& key) override { return Left() == key; } + virtual bool Right(const std::string& key) override { return Right() == key; } + virtual bool Next(const std::string& key) override { return Next() == key; } + virtual bool Prev(const std::string& key) override { return Prev() == key; } + virtual bool Mode(const std::string& key) override { return Mode() == key; } + virtual bool PageUp(const std::string& key) override { return PageUp() == key; } + virtual bool PageDown(const std::string& key) override { return PageDown() == key; } + virtual bool Home(const std::string& key) override { return Home() == key; } + virtual bool End(const std::string& key) override { return End() == key; } + + virtual std::string Up() override { return "KEY_UP"; } + virtual std::string Down() override { return "KEY_DOWN"; } + virtual std::string Left() override { return "KEY_LEFT"; } + virtual std::string Right() override { return "KEY_RIGHT"; } + virtual std::string Next() override { return "KEY_TAB"; } + virtual std::string Prev() override { return "KEY_BTAB"; } + virtual std::string Mode() override { return "^["; } + virtual std::string PageUp() override { return "KEY_PPAGE"; } + virtual std::string PageDown() override { return "KEY_NPAGE"; } + virtual std::string Home() override { return "KEY_HOME"; } + virtual std::string End() override { return "KEY_END"; } +} defaultNavigationKeys; + +INavigationKeys& Window::NavigationKeys() { + if (::keys) { + return *::keys.get(); + } + + return defaultNavigationKeys; +} \ No newline at end of file diff --git a/src/musikcube/cursespp/Window.h b/src/musikcube/cursespp/Window.h index 98a8856de..3d6e40a17 100755 --- a/src/musikcube/cursespp/Window.h +++ b/src/musikcube/cursespp/Window.h @@ -36,7 +36,7 @@ #include "curses_config.h" #include "IWindow.h" - +#include "INavigationKeys.h" #include #ifdef WIN32 @@ -123,6 +123,8 @@ namespace cursespp { static void Freeze(); static void Unfreeze(); + static void SetNavigationKeys(std::shared_ptr keys); + static musik::core::runtime::IMessageQueue& MessageQueue(); protected: @@ -131,6 +133,7 @@ namespace cursespp { void PostMessage(int messageType, int64_t user1 = 0, int64_t user2 = 0, int64_t delay = 0); void DebounceMessage(int messageType, int64_t user1 = 0, int64_t user2 = 0, int64_t delay = 0); void RemoveMessage(int messageType); + static INavigationKeys& NavigationKeys(); virtual void Create(); virtual void Destroy(); diff --git a/src/musikcube/musikcube.vcxproj b/src/musikcube/musikcube.vcxproj index 346701d90..2e2dc2318 100755 --- a/src/musikcube/musikcube.vcxproj +++ b/src/musikcube/musikcube.vcxproj @@ -227,6 +227,7 @@ xcopy "$(SolutionDir)src\plugins\websocket_remote\3rdparty\win32_bin\$(Configura + diff --git a/src/musikcube/musikcube.vcxproj.filters b/src/musikcube/musikcube.vcxproj.filters index d41abea09..6a6ccfe33 100755 --- a/src/musikcube/musikcube.vcxproj.filters +++ b/src/musikcube/musikcube.vcxproj.filters @@ -337,6 +337,9 @@ cursespp\util + + cursespp +