Skip to content

Commit

Permalink
Add One-Hand mice wheel scroll diff and merge (#2435) (5). Alt+Wheel …
Browse files Browse the repository at this point in the history
…Down and other commands now work not only in the editor but also in other windows.
sdottaka committed Oct 8, 2024
1 parent 91bfd52 commit 5ddf723
Showing 7 changed files with 213 additions and 132 deletions.
3 changes: 3 additions & 0 deletions Src/DirView.cpp
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@
#include "SyntaxColors.h"
#include "Shell.h"
#include "DirTravel.h"
#include "LowLevelMouseHook.h"
#include <numeric>
#include <functional>

@@ -638,6 +639,8 @@ void CDirView::Redisplay()
*/
void CDirView::OnContextMenu(CWnd*, CPoint point)
{
if (CLowLevelMouseHook::IsRightWheelScrolling())
return;
if (GetListCtrl().GetItemCount() == 0)
return;
// Make sure window is active
188 changes: 188 additions & 0 deletions Src/LowLevelMouseHook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#include <StdAfx.h>
#include "LowLevelMouseHook.h"

void CALLBACK CLowLevelMouseHook::TimerProc(HWND unnamedParam1, UINT unnamedParam2, UINT_PTR id, DWORD unnamedParam4HWND)
{
KillTimer(nullptr, id);
EndMenu();
m_bIgnoreRBUp = false;
}

LRESULT CALLBACK CLowLevelMouseHook::LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode < 0)
return CallNextHookEx(m_hMouseHook, nCode, wParam, lParam);

if (wParam == WM_RBUTTONDOWN)
{
m_bRButtonDown = true;
}
else if (wParam == WM_RBUTTONUP)
{
m_bRButtonDown = false;
if (m_bIgnoreRBUp)
{
LRESULT result = CallNextHookEx(m_hMouseHook, nCode, wParam, lParam);
SetTimer(nullptr, 0, USER_TIMER_MINIMUM, TimerProc);
return result;
}
}
else if (wParam == WM_MOUSEWHEEL)
{
MSLLHOOKSTRUCT* pMouseStruct = (MSLLHOOKSTRUCT*)lParam;
int zDelta = GET_WHEEL_DELTA_WPARAM(pMouseStruct->mouseData);
int nFlags = pMouseStruct->flags;

if (GetKeyState(VK_MENU) & 0x8000)
{
HWND hwndTarget = GetForegroundWindow();
// When hold Alt key, use nFlags to check MK_CONTROL MK_SHIFT holding got problem, Use GetKeyState() instead.
const auto bShiftDown = GetKeyState(VK_SHIFT) & 0x8000;
const auto bControlDown = GetKeyState(VK_CONTROL) & 0x8000;
// zDelta > 0 scrool up, < 0 scrool down
if (zDelta > 0)
{
// Check Shift key hold for mice without HWheel function
if (bShiftDown && bControlDown)
{
// Alt+Ctrl+Shift+ScrollUp as Alt+Ctrl+Left
PostMessage(hwndTarget, WM_COMMAND, ID_R2LNEXT, 0);
return 1;
}
else if (bShiftDown)
{
// Alt+Shift+ScrollUp as Alt+Left
PostMessage(hwndTarget, WM_COMMAND, ID_R2L, 0);
return 1;
}
else if (nFlags == 0)
{
// Alt+ScrollUp as Alt+Up
PostMessage(hwndTarget, WM_COMMAND, ID_PREVDIFF, 0);
return 1;
}
}
else if (zDelta < 0)
{
// Check Shift key hold for mice without HWheel function
if (bShiftDown && bControlDown)
{
// Alt+Ctrl+Shift+ScrollDown as Alt+Ctrl+Right
PostMessage(hwndTarget, WM_COMMAND, ID_L2RNEXT, 0);
return 1;
}
else if (bShiftDown)
{
// Alt+Shift+ScrollDown as Alt+Right
PostMessage(hwndTarget, WM_COMMAND, ID_L2R, 0);
return 1;
}
else if (nFlags == 0)
{
// Alt+ScrollDown as Alt+Down
PostMessage(hwndTarget, WM_COMMAND, ID_NEXTDIFF, 0);
return 1;
}
}
}

// Hold mice right button for One-handed operation
if (m_bRButtonDown)
{
HWND hwndTarget = GetForegroundWindow();
if (zDelta > 0)
{
// RButton+ScrollUp as Alt+Up
m_bIgnoreRBUp = true;
PostMessage(hwndTarget, WM_COMMAND, ID_PREVDIFF, 0);
return 1;
}
else if (zDelta < 0)
{
// RButton+ScrollDown as Alt+Down
m_bIgnoreRBUp = true;
PostMessage(hwndTarget, WM_COMMAND, ID_NEXTDIFF, 0);
return 1;
}
}
}
else if (wParam == WM_MOUSEHWHEEL)
{
MSLLHOOKSTRUCT* pMouseStruct = (MSLLHOOKSTRUCT*)lParam;
int zDelta = GET_WHEEL_DELTA_WPARAM(pMouseStruct->mouseData);
int nFlags = pMouseStruct->flags;

if (GetKeyState(VK_MENU) & 0x8000)
{
HWND hwndTarget = GetForegroundWindow();
const auto bControlDown = GetKeyState(VK_CONTROL) & 0x8000;
// zDelta > 0 scrool right, < 0 scrool left
if (zDelta > 0)
{
if (bControlDown)
{
// Alt+Ctrl+HScrollRight as Alt+Ctrl+Right
PostMessage(hwndTarget, WM_COMMAND, ID_L2RNEXT, 0);
return 1;
}
else if (nFlags == 0)
{
// Alt+HScrollRight as Alt+Right
PostMessage(hwndTarget, WM_COMMAND, ID_L2R, 0);
return 1;
}
}
else if (zDelta < 0)
{
if (bControlDown)
{
// Alt+Ctrl+HScrollLeft as Alt+Ctrl+Left
PostMessage(hwndTarget, WM_COMMAND, ID_R2LNEXT, 0);
return 1;
}
else if (nFlags == 0)
{
// Alt+HScrollLeft as Alt+Left
PostMessage(hwndTarget, WM_COMMAND, ID_R2L, 0);
return 1;
}
}
}

// Hold mice right button for One-handed operation
if (m_bRButtonDown)
{
HWND hwndTarget = GetForegroundWindow();
if (zDelta > 0)
{
// RButton+ScrollRight as Alt+Right
m_bIgnoreRBUp = true;
PostMessage(hwndTarget, WM_COMMAND, ID_L2R, 0);
return 1;
}
else if (zDelta < 0)
{
// RButton+ScrollLeft as Alt+Left
m_bIgnoreRBUp = true;
PostMessage(hwndTarget, WM_COMMAND, ID_R2L, 0);
return 1;
}
}
}
return CallNextHookEx(m_hMouseHook, nCode, wParam, lParam);
}

void CLowLevelMouseHook::SetMouseHook()
{
m_hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, nullptr, 0);
}

void CLowLevelMouseHook::UnhookMouseHook()
{
if (m_hMouseHook)
{
UnhookWindowsHookEx(m_hMouseHook);
m_hMouseHook = nullptr;
}
}

13 changes: 13 additions & 0 deletions Src/LowLevelMouseHook.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class CLowLevelMouseHook
{
public:
static void SetMouseHook();
static void UnhookMouseHook();
static bool IsRightWheelScrolling() { return m_bIgnoreRBUp; }
private:
static LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam);
static void CALLBACK TimerProc(HWND unnamedParam1, UINT unnamedParam2, UINT_PTR id, DWORD unnamedParam4HWND);
inline static HHOOK m_hMouseHook;
inline static bool m_bIgnoreRBUp;
inline static bool m_bRButtonDown;
};
5 changes: 5 additions & 0 deletions Src/Merge.cpp
Original file line number Diff line number Diff line change
@@ -64,6 +64,7 @@
#include "RegKey.h"
#include "Win_VersionHelper.h"
#include "BCMenu.h"
#include "LowLevelMouseHook.h"

#ifdef _DEBUG
#define new DEBUG_NEW
@@ -423,6 +424,8 @@ BOOL CMergeApp::InitInstance()
return FALSE;
}

CLowLevelMouseHook::SetMouseHook();

// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
@@ -561,6 +564,8 @@ void CMergeApp::OnAppAbout()
*/
int CMergeApp::ExitInstance()
{
CLowLevelMouseHook::UnhookMouseHook();

charsets_cleanup();

// Save registry keys if existing WinMerge.reg
2 changes: 2 additions & 0 deletions Src/Merge.vcxproj
Original file line number Diff line number Diff line change
@@ -945,6 +945,7 @@
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)$(TargetName)2.pch</PrecompiledHeaderOutputFile>
</ClCompile>
<ClCompile Include="LowLevelMouseHook.cpp" />
<ClCompile Include="MergeDocDiffCopy.cpp" />
<ClCompile Include="MyReBar.cpp" />
<ClCompile Include="PropCompareWebPage.cpp" />
@@ -1447,6 +1448,7 @@
<ClInclude Include="AboutDlg.h" />
<ClInclude Include="BasicFlatStatusBar.h" />
<ClInclude Include="ClipboardHistory.h" />
<ClInclude Include="LowLevelMouseHook.h" />
<ClInclude Include="MenuBar.h" />
<ClInclude Include="Common\cio.h" />
<ClInclude Include="Common\DebugNew.h" />
133 changes: 2 additions & 131 deletions Src/MergeEditView.cpp
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@
#include "Shell.h"
#include "SelectPluginDlg.h"
#include "Constants.h"
#include "LowLevelMouseHook.h"

#ifdef _DEBUG
#define new DEBUG_NEW
@@ -59,7 +60,6 @@ IMPLEMENT_DYNCREATE(CMergeEditView, CCrystalEditViewEx)

CMergeEditView::CMergeEditView()
: m_bCurrentLineIsDiff(false)
, m_bIgnoreRBUp(false)
, m_nThisPane(0)
, m_nThisGroup(0)
, m_bDetailView(false)
@@ -2869,11 +2869,8 @@ void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
*/
void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
{
if (m_bIgnoreRBUp)
{
m_bIgnoreRBUp = false;
if (CLowLevelMouseHook::IsRightWheelScrolling())
return;
}

CRect rect;
GetClientRect(rect);
@@ -4268,77 +4265,6 @@ void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
*/
BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
if (GetKeyState(VK_MENU) & 0x8000)
{
// When hold Alt key, use nFlags to check MK_CONTROL MK_SHIFT holding got problem, Use GetKeyState() instead.
const auto bShiftDown = GetKeyState(VK_SHIFT) & 0x8000;
const auto bControlDown = GetKeyState(VK_CONTROL) & 0x8000;
// zDelta > 0 scrool up, < 0 scrool down
if (zDelta > 0)
{
// Check Shift key hold for mice without HWheel function
if (bShiftDown && bControlDown)
{
// Alt+Ctrl+Shift+ScrollUp as Alt+Ctrl+Left
OnR2LNext();
return TRUE;
}
else if (bShiftDown)
{
// Alt+Shift+ScrollUp as Alt+Left
OnR2l();
return TRUE;
}
else if (nFlags == 0)
{
// Alt+ScrollUp as Alt+Up
OnPrevdiff();
return TRUE;
}
}
else if (zDelta < 0)
{
// Check Shift key hold for mice without HWheel function
if (bShiftDown && bControlDown)
{
// Alt+Ctrl+Shift+ScrollDown as Alt+Ctrl+Right
OnL2RNext();
return TRUE;
}
else if (bShiftDown)
{
// Alt+Shift+ScrollDown as Alt+Right
OnL2r();
return TRUE;
}
else if (nFlags == 0)
{
// Alt+ScrollDown as Alt+Down
OnNextdiff();
return TRUE;
}
}
}

// Hold mice right button for One-handed operation
if (nFlags == MK_RBUTTON)
{
if (zDelta > 0)
{
// RButton+ScrollUp as Alt+Up
OnPrevdiff();
m_bIgnoreRBUp = true;
return TRUE;
}
else if (zDelta < 0)
{
// RButton+ScrollDown as Alt+Down
OnNextdiff();
m_bIgnoreRBUp = true;
return TRUE;
}
}

if ( nFlags == MK_CONTROL )
{
short amount = zDelta < 0 ? -1: 1;
@@ -4377,61 +4303,6 @@ BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
*/
void CMergeEditView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
{
if (GetKeyState(VK_MENU) & 0x8000)
{
const auto bControlDown = GetKeyState(VK_CONTROL) & 0x8000;
// zDelta > 0 scrool right, < 0 scrool left
if (zDelta > 0)
{
if (bControlDown)
{
// Alt+Ctrl+HScrollRight as Alt+Ctrl+Right
OnL2RNext();
return;
}
else if (nFlags == 0)
{
// Alt+HScrollRight as Alt+Right
OnL2r();
return;
}
}
else if (zDelta < 0)
{
if (bControlDown)
{
// Alt+Ctrl+HScrollLeft as Alt+Ctrl+Left
OnR2LNext();
return;
}
else if (nFlags == 0)
{
// Alt+HScrollLeft as Alt+Left
OnR2l();
return;
}
}
}

// Hold mice right button for One-handed operation
if (nFlags == MK_RBUTTON)
{
if (zDelta > 0)
{
// RButton+ScrollRight as Alt+Right
OnL2r();
m_bIgnoreRBUp = true;
return;
}
else if (zDelta < 0)
{
// RButton+ScrollLeft as Alt+Left
OnR2l();
m_bIgnoreRBUp = true;
return;
}
}

SCROLLINFO si = { sizeof SCROLLINFO };
si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;

1 change: 0 additions & 1 deletion Src/MergeEditView.h
Original file line number Diff line number Diff line change
@@ -86,7 +86,6 @@ class CMergeEditView : public CGhostTextView
COLORSETTINGS m_cachedColors; /**< Cached color settings */

bool m_bCurrentLineIsDiff; /**< `true` if cursor is in diff line */
bool m_bIgnoreRBUp;

// Attributes
public:

3 comments on commit 5ddf723

@lededev
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the Windows file explorer right-click menu to open two WinMerge comparison windows. Then, when the right button of the second window is pressed, the scroll wheel is operated, and a menu will pop up when the right button is released.

The first comparison window opened can normally block this right button release action.

@lededev
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion is not to use the WH_MOUSE_LL hook. Using a global hook may cause many side effects. For each MFC window that needs this function, you can handle pMsg->message == WM_MOUSEWHEEL and pMsg->message == WM_MOUSEHWHEEL in advance in PreTranslateMessage(pMsg).

@sdottaka
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently in an environment without a mouse so I can't test it, but it's not good to have it be a global hook, so I changed it to use WH_MOUSE instead of WH_MOUSE_LL.

Please sign in to comment.