Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Ctrl-Tab to Playlist tabs and Tab stack #817

Merged
merged 1 commit into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@

### Features

- Ctrl+Tab and Shift+Ctrl+Tab can now be used in Tab stack and Playlist tabs to
switch to the next and previous tab respectively.
[[#817](https://github.com/reupen/columns_ui/pull/817)]

### Bug fixes

- When switching tabs, the Tab stack panel now updates the keyboard focus to the
first focusable element in the new tab.
[[#817](https://github.com/reupen/columns_ui/pull/817)]

## v2.1.0

### Features

- Dark menus were enabled on Windows 11 builds greater than 22631.
[[#788](https://github.com/reupen/columns_ui/pull/788), contributed by
[@razielanarki](https://github.com/razielanarki)]

### Internal changes

- The component is now compiled using foobar2000 SDK 2023-05-10.
[[#799](https://github.com/reupen/columns_ui/pull/799)]

### Bug fixes

- A bug where the empty area at the bottom of a playlist view with a small
Expand All @@ -34,6 +43,13 @@
- The copyright year was updated.
[[#803](https://github.com/reupen/columns_ui/pull/803)]

### Internal changes

- The component is now compiled using foobar2000 SDK 2023-05-10.
[[#799](https://github.com/reupen/columns_ui/pull/799)]

- The component is now compiled with Visual Studio 2022 17.7.

## 2.1.0-beta.3

### Bug fixes
Expand Down
5 changes: 4 additions & 1 deletion foo_ui_columns/get_msg_hook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ bool GetMsgHook::on_hooked_message(uih::MessageHookType p_type, int code, WPARAM
g_layout_window.hide_menu_access_keys();
}
} else if (lpmsg->wParam == VK_TAB) {
const auto flags = SendMessage(lpmsg->hwnd, WM_GETDLGCODE, 0, (LPARAM)lpmsg);
if (GetKeyState(VK_CONTROL) & 0x8000)
return false;

const auto flags = SendMessage(lpmsg->hwnd, WM_GETDLGCODE, 0, reinterpret_cast<LPARAM>(lpmsg));
if (!((flags & DLGC_WANTTAB) || (flags & DLGC_WANTMESSAGE))) {
ui_extension::window::g_on_tab(lpmsg->hwnd);
lpmsg->message = WM_NULL;
Expand Down
71 changes: 71 additions & 0 deletions foo_ui_columns/helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,75 @@ void clip_minmaxinfo(MINMAXINFO& mmi)
mmi.ptMaxTrackSize.x = std::min(mmi.ptMaxTrackSize.x, static_cast<long>(MAXSHORT));
}

namespace {

bool is_tab_control(HWND wnd)
{
std::array<wchar_t, 256> class_name{};
RealGetWindowClass(wnd, class_name.data(), gsl::narrow<UINT>(class_name.size()));
return wcsncmp(class_name.data(), WC_TABCONTROL, class_name.size()) == 0;
}

HWND find_parent_tab_control(HWND child, HWND root)
{
if (is_tab_control(child))
return child;

HWND wnd = child;

while (wnd != root) {
HWND next = wnd;
while ((next = GetWindow(next, GW_HWNDNEXT))) {
if (is_tab_control(next)) {
return next;
}
}

wnd = GetAncestor(wnd, GA_PARENT);
}

return nullptr;
}

} // namespace

void handle_tabs_ctrl_tab(MSG* msg, HWND wnd_container, HWND wnd_tabs)
{
if (msg->message != WM_KEYDOWN || msg->wParam != VK_TAB)
return;

if (msg->hwnd != wnd_container && !IsChild(wnd_container, msg->hwnd))
return;

if ((GetKeyState(VK_CONTROL) & 0x8000) == 0)
return;

const auto closest_tab_control = find_parent_tab_control(msg->hwnd, wnd_container);

if (closest_tab_control && closest_tab_control != wnd_tabs)
return;

// Don't bother with sending and checking WM_GETDLGCODE – too many false positives

msg->message = WM_NULL;
msg->lParam = 0;
msg->wParam = 0;

const auto index = TabCtrl_GetCurSel(wnd_tabs);
const auto count = TabCtrl_GetItemCount(wnd_tabs);

if (count == 0)
return;

const auto new_index = [=] {
if (GetKeyState(VK_SHIFT) & 0x8000)
return index <= 0 ? count - 1 : index - 1;

return index < 0 || index + 1 == count ? 0 : index + 1;
}();

if (index != new_index)
TabCtrl_SetCurFocus(wnd_tabs, new_index);
}

} // namespace cui::helpers
2 changes: 2 additions & 0 deletions foo_ui_columns/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ std::vector<HWND> get_child_windows(HWND wnd, std::function<bool(HWND)> filter =
pfc::string8 get_last_win32_error_message();
bool open_web_page(HWND wnd, const wchar_t* url);
void clip_minmaxinfo(MINMAXINFO& mmi);
void handle_tabs_ctrl_tab(MSG* msg, HWND wnd_container, HWND wnd_tabs);

} // namespace cui::helpers
1 change: 1 addition & 0 deletions foo_ui_columns/playlist_tabs.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ class PlaylistTabs

MINMAXINFO mmi{};
std::unique_ptr<colours::dark_mode_notifier> m_dark_mode_notifier;
std::unique_ptr<uih::EventToken> m_get_message_hook_token;
};

extern ui_extension::window_host_factory<PlaylistTabs::WindowHost> g_tab_host;
Expand Down
7 changes: 7 additions & 0 deletions foo_ui_columns/playlist_tabs_wndproc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ LRESULT PlaylistTabs::on_message(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
RedrawWindow(wnd, nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE);
RedrawWindow(wnd_tabs, nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE);
});

m_get_message_hook_token = uih::register_message_hook(
uih::MessageHookType::type_get_message, [wnd, this](int code, WPARAM wp, LPARAM lp) -> bool {
helpers::handle_tabs_ctrl_tab(reinterpret_cast<LPMSG>(lp), wnd, wnd_tabs);
return false;
});
break;
}
case WM_SHOWWINDOW: {
Expand All @@ -80,6 +86,7 @@ LRESULT PlaylistTabs::on_message(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
}
return 0;
case WM_DESTROY: {
m_get_message_hook_token.reset();
m_dark_mode_notifier.reset();
playlist_manager::get()->unregister_callback(this);
destroy_child();
Expand Down
67 changes: 46 additions & 21 deletions foo_ui_columns/splitter_tabs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,14 @@ LRESULT TabStackPanel::on_message(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
RedrawWindow(wnd, nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE);
RedrawWindow(wnd_tabs, nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE);
});
} break;

m_get_message_hook_token = uih::register_message_hook(
uih::MessageHookType::type_get_message, [this, wnd](int code, WPARAM wp, LPARAM lp) -> bool {
helpers::handle_tabs_ctrl_tab(reinterpret_cast<LPMSG>(lp), wnd, m_wnd_tabs);
return false;
});
break;
}
case WM_KEYDOWN: {
if (wp != VK_LEFT && wp != VK_RIGHT && get_host()->get_keyboard_shortcuts_enabled()
&& g_process_keydown_keyboard_shortcuts(wp))
Expand All @@ -553,6 +560,7 @@ LRESULT TabStackPanel::on_message(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
return 0;
break;
case WM_DESTROY:
m_get_message_hook_token.reset();
m_dark_mode_notifier.reset();
std::erase(g_windows, this);
destroy_children();
Expand Down Expand Up @@ -654,24 +662,7 @@ LRESULT TabStackPanel::on_message(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
case 2345:
switch (((LPNMHDR)lp)->code) {
case TCN_SELCHANGE:
on_active_tab_changed(TabCtrl_GetCurSel(m_wnd_tabs));
if (*m_active_tab && *m_active_tab < m_active_panels.get_count() && GetFocus() != m_wnd_tabs) {
HWND wnd_root = GetAncestor(m_wnd_tabs, GA_ROOT);
HWND wnd_focus = nullptr;
if (wnd_root) {
wnd_focus = m_active_panels[*m_active_tab]->m_wnd;
if (!(GetWindowLongPtr(wnd_focus, GWL_STYLE) & WS_TABSTOP))
wnd_focus = GetNextDlgTabItem(wnd_root, m_wnd_tabs, FALSE);
if (wnd_focus
&& (IsChild(m_active_panels[*m_active_tab]->m_wnd, wnd_focus)
|| m_active_panels[*m_active_tab]->m_wnd == wnd_focus))
SetFocus(wnd_focus);
else
wnd_focus = nullptr;
}
if (!wnd_focus)
SetFocus(m_wnd_tabs);
}
on_active_tab_changed(TabCtrl_GetCurSel(m_wnd_tabs), true);
break;
}
break;
Expand All @@ -683,7 +674,7 @@ LRESULT TabStackPanel::on_message(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)

void TabStackPanel::show_tab_window(HWND wnd)
{
assert(!m_active_child_wnd);
assert(!m_active_child_wnd || wnd == m_active_child_wnd);
m_active_child_wnd = wnd;
ShowWindow(wnd, SW_SHOWNORMAL);
}
Expand Down Expand Up @@ -938,8 +929,12 @@ void TabStackPanel::on_size_changed()
on_size_changed(wil::rect_width(rc), wil::rect_height(rc));
}

void TabStackPanel::on_active_tab_changed(int signed_index_to)
void TabStackPanel::on_active_tab_changed(int signed_index_to, bool from_interaction)
{
const auto wnd_focus = GetFocus();
const auto was_child_focused
= wnd_focus && (wnd_focus == m_active_child_wnd || IsChild(m_active_child_wnd, wnd_focus));

hide_tab_window();
m_active_tab.reset();

Expand All @@ -956,6 +951,36 @@ void TabStackPanel::on_active_tab_changed(int signed_index_to)
if (found_index != std::numeric_limits<size_t>::max())
m_active_tab = found_index;
}

if (!m_active_tab || wnd_focus == m_wnd_tabs || (!from_interaction && !was_child_focused))
return;

const HWND wnd_root = GetAncestor(m_wnd_tabs, GA_ROOT);

if (!wnd_root)
return;

const auto wnd_child = m_panels[*m_active_tab]->m_wnd;

HWND wnd_new_focus = wnd_child;

if (!(GetWindowLongPtr(wnd_new_focus, GWL_STYLE) & WS_TABSTOP))
wnd_new_focus = GetNextDlgTabItem(wnd_new_focus, wnd_new_focus, FALSE);

const auto should_focus = [=] {
if (!wnd_new_focus)
return false;

if (wnd_child != wnd_new_focus && !IsChild(wnd_child, wnd_new_focus))
return false;

return (GetWindowLongPtr(wnd_new_focus, GWL_STYLE) & WS_TABSTOP) != 0;
}();

if (should_focus)
SetFocus(wnd_new_focus);
else
SetFocus(m_wnd_tabs);
}

LRESULT WINAPI TabStackPanel::g_hook_proc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
Expand Down
3 changes: 2 additions & 1 deletion foo_ui_columns/splitter_tabs.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ class TabStackPanel : public uie::container_uie_window_v3_t<uie::splitter_window
static void g_on_font_change();
void on_size_changed(unsigned width, unsigned height);
void on_size_changed();
void on_active_tab_changed(int index_to);
void on_active_tab_changed(int index_to, bool from_interaction = false);

TabStackPanel() = default;

Expand All @@ -148,6 +148,7 @@ class TabStackPanel : public uie::container_uie_window_v3_t<uie::splitter_window
int32_t m_mousewheel_delta{NULL};
wil::unique_hfont g_font;
std::unique_ptr<colours::dark_mode_notifier> m_dark_mode_notifier;
std::unique_ptr<uih::EventToken> m_get_message_hook_token;
};

} // namespace cui::panels::tab_stack
Loading