1 /*
2  * PROJECT:     ReactOS Clipboard Viewer
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Scrolling related helper functions.
5  * COPYRIGHT:   Copyright 2015-2018 Ricardo Hanke
6  *              Copyright 2015-2018 Hermes Belusca-Maito
7  */
8 
9 #include "precomp.h"
10 
11 void OnKeyScroll(HWND hWnd, WPARAM wParam, LPARAM lParam, LPSCROLLSTATE state)
12 {
13     // NOTE: Windows uses an offset of 16 pixels
14     switch (wParam)
15     {
16         case VK_UP:
17             OnScroll(hWnd, SB_VERT, MAKELONG(SB_LINEUP, 0), 5, state);
18             break;
19 
20         case VK_DOWN:
21             OnScroll(hWnd, SB_VERT, MAKELONG(SB_LINEDOWN, 0), 5, state);
22             break;
23 
24         case VK_LEFT:
25             OnScroll(hWnd, SB_HORZ, MAKELONG(SB_LINELEFT, 0), 5, state);
26             break;
27 
28         case VK_RIGHT:
29             OnScroll(hWnd, SB_HORZ, MAKELONG(SB_LINERIGHT, 0), 5, state);
30             break;
31 
32         case VK_PRIOR:
33             OnScroll(hWnd, SB_VERT, MAKELONG(SB_PAGEUP, 0), state->nPageY, state);
34             break;
35 
36         case VK_NEXT:
37             OnScroll(hWnd, SB_VERT, MAKELONG(SB_PAGEDOWN, 0), state->nPageY, state);
38             break;
39 
40         case VK_HOME:
41         {
42             OnScroll(hWnd, SB_HORZ, MAKELONG(SB_LEFT, 0), 0, state);
43             if (GetKeyState(VK_CONTROL) & 0x8000)
44                 OnScroll(hWnd, SB_VERT, MAKELONG(SB_TOP, 0), 0, state);
45             break;
46         }
47 
48         case VK_END:
49         {
50             OnScroll(hWnd, SB_HORZ, MAKELONG(SB_RIGHT, 0), 0, state);
51             if (GetKeyState(VK_CONTROL) & 0x8000)
52                 OnScroll(hWnd, SB_VERT, MAKELONG(SB_BOTTOM, 0), 0, state);
53             break;
54         }
55 
56         default:
57             break;
58     }
59 }
60 
61 void OnMouseScroll(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LPSCROLLSTATE state)
62 {
63     INT nBar;
64     INT nPage;
65     INT iDelta;
66     UINT uLinesToScroll = state->uLinesToScroll;
67     INT zDelta = GET_WHEEL_DELTA_WPARAM(wParam);
68     WORD sbCode;
69 
70     assert(uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL);
71 
72     if (uMsg == WM_MOUSEWHEEL)
73     {
74         nBar = SB_VERT;
75         nPage = state->nPageY;
76 
77         /* Accumulate wheel rotation ticks */
78         zDelta += state->iWheelCarryoverY;
79         state->iWheelCarryoverY = zDelta % WHEEL_DELTA;
80     }
81     else // if (uMsg == WM_MOUSEHWHEEL)
82     {
83         nBar = SB_HORZ;
84         nPage = state->nPageX;
85 
86         /* Accumulate wheel rotation ticks */
87         zDelta += state->iWheelCarryoverX;
88         state->iWheelCarryoverX = zDelta % WHEEL_DELTA;
89     }
90 
91     /*
92      * If the user specified scrolling by pages, do so.
93      * Due to a bug on Windows where, if the window height is
94      * less than the scroll lines delta default value (== 3),
95      * several lines would be skipped when scrolling if we
96      * used the WHEEL_PAGESCROLL value. Instead of this, use
97      * the number of lines per page as the limiting value.
98      * See https://www.strchr.com/corrections_to_raymond_chen_s_wheel_scrolling_code
99      * for more details.
100      */
101     if (uLinesToScroll > nPage) // (uLinesToScroll == WHEEL_PAGESCROLL)
102         uLinesToScroll = nPage;
103     /* If the user specified no wheel scrolling, don't do anything */
104     else if (uLinesToScroll == 0)
105         return;
106 
107     /* Compute the scroll direction and the absolute delta value */
108     if (zDelta > 0)
109     {
110         sbCode = SB_LINEUP;
111     }
112     else
113     {
114         sbCode = SB_LINEDOWN;
115         zDelta = -zDelta;
116     }
117 
118     /* Compute how many lines we should scroll (in absolute value) */
119     iDelta = (INT)uLinesToScroll * zDelta / WHEEL_DELTA;
120 
121     OnScroll(hWnd, nBar, MAKELONG(sbCode, 0), iDelta, state);
122 }
123 
124 void OnScroll(HWND hWnd, INT nBar, WPARAM wParam, INT iDelta, LPSCROLLSTATE state)
125 {
126     SCROLLINFO si;
127     PINT pCurrent;
128     INT Maximum;
129     INT NewPos;
130     INT OldX, OldY;
131 
132     assert(nBar == SB_HORZ || nBar == SB_VERT);
133 
134     if (Globals.uDisplayFormat == CF_OWNERDISPLAY)
135     {
136         if (nBar == SB_HORZ)
137         {
138             SendClipboardOwnerMessage(TRUE, WM_HSCROLLCLIPBOARD,
139                                       (WPARAM)hWnd, (LPARAM)wParam);
140         }
141         else // if (nBar == SB_VERT)
142         {
143             SendClipboardOwnerMessage(TRUE, WM_VSCROLLCLIPBOARD,
144                                       (WPARAM)hWnd, (LPARAM)wParam);
145         }
146         return;
147     }
148 
149     if (nBar == SB_HORZ)
150     {
151         pCurrent = &state->CurrentX;
152         Maximum = state->MaxX;
153     }
154     else // if (nBar == SB_VERT)
155     {
156         pCurrent = &state->CurrentY;
157         Maximum = state->MaxY;
158     }
159 
160     ZeroMemory(&si, sizeof(si));
161     si.cbSize = sizeof(si);
162     si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS;
163     GetScrollInfo(hWnd, nBar, &si);
164 
165     switch (LOWORD(wParam))
166     {
167         case SB_THUMBPOSITION:
168         case SB_THUMBTRACK:
169         {
170             NewPos = si.nTrackPos;
171             break;
172         }
173 
174         case SB_LINEUP:     // SB_LINELEFT:
175         {
176             NewPos = si.nPos - iDelta;
177             break;
178         }
179 
180         case SB_LINEDOWN:   // SB_LINERIGHT:
181         {
182             NewPos = si.nPos + iDelta;
183             break;
184         }
185 
186         case SB_PAGEUP:     // SB_PAGELEFT:
187         {
188             NewPos = si.nPos - si.nPage;
189             break;
190         }
191 
192         case SB_PAGEDOWN:   // SB_PAGERIGHT:
193         {
194             NewPos = si.nPos + si.nPage;
195             break;
196         }
197 
198         case SB_TOP:        // SB_LEFT:
199         {
200             NewPos = si.nMin;
201             break;
202         }
203 
204         case SB_BOTTOM:     // SB_RIGHT:
205         {
206             NewPos = si.nMax;
207             break;
208         }
209 
210         default:
211         {
212             NewPos = si.nPos;
213             break;
214         }
215     }
216 
217     NewPos = min(max(NewPos, 0), Maximum);
218 
219     if (si.nPos == NewPos)
220         return;
221 
222     OldX = state->CurrentX;
223     OldY = state->CurrentY;
224     *pCurrent = NewPos;
225 
226     ScrollWindowEx(hWnd,
227                    OldX - state->CurrentX,
228                    OldY - state->CurrentY,
229                    NULL,
230                    NULL,
231                    NULL,
232                    NULL,
233                    SW_ERASE | SW_INVALIDATE);
234     UpdateWindow(hWnd);
235 
236     si.fMask = SIF_POS;
237     si.nPos = NewPos;
238     SetScrollInfo(hWnd, nBar, &si, TRUE);
239 }
240 
241 void UpdateLinesToScroll(LPSCROLLSTATE state)
242 {
243     UINT uLinesToScroll;
244 
245     if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &uLinesToScroll, 0))
246     {
247         /* Default value on Windows */
248         state->uLinesToScroll = 3;
249     }
250     else
251     {
252         state->uLinesToScroll = uLinesToScroll;
253     }
254 }
255 
256 void UpdateWindowScrollState(HWND hWnd, INT nMaxWidth, INT nMaxHeight, LPSCROLLSTATE lpState)
257 {
258     RECT rc;
259     SCROLLINFO si;
260 
261     if (!GetClientRect(hWnd, &rc))
262         SetRectEmpty(&rc);
263 
264     ZeroMemory(&si, sizeof(si));
265     si.cbSize = sizeof(si);
266     si.fMask  = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_DISABLENOSCROLL;
267 
268     lpState->nMaxWidth = nMaxWidth;
269     lpState->MaxX = max(nMaxWidth - rc.right, 0);
270     lpState->CurrentX = min(lpState->CurrentX, lpState->MaxX);
271     lpState->nPageX = rc.right;
272     si.nMin  = 0;
273     si.nMax  = nMaxWidth;
274     si.nPage = lpState->nPageX;
275     si.nPos  = lpState->CurrentX;
276     SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
277 
278     lpState->nMaxHeight = nMaxHeight;
279     lpState->MaxY = max(nMaxHeight - rc.bottom, 0);
280     lpState->CurrentY = min(lpState->CurrentY, lpState->MaxY);
281     lpState->nPageY = rc.bottom;
282     si.nMin  = 0;
283     si.nMax  = nMaxHeight;
284     si.nPage = lpState->nPageY;
285     si.nPos  = lpState->CurrentY;
286     SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
287 }
288