[COMCTL32]
[reactos.git] / reactos / dll / win32 / comctl32 / pager.c
1 /*
2 * Pager control
3 *
4 * Copyright 1998, 1999 Eric Kohl
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 *
20 * NOTES
21 *
22 * This code was audited for completeness against the documented features
23 * of Comctl32.dll version 6.0 on Sep. 18, 2004, by Robert Shearman.
24 *
25 * Unless otherwise noted, we believe this code to be complete, as per
26 * the specification mentioned above.
27 * If you discover missing features or bugs please note them below.
28 *
29 * TODO:
30 * Implement repetitive button press.
31 * Adjust arrow size relative to size of button.
32 * Allow border size changes.
33 * Styles:
34 * PGS_DRAGNDROP
35 * Notifications:
36 * PGN_HOTITEMCHANGE
37 * Messages:
38 * WM_PRINT and/or WM_PRINTCLIENT
39 *
40 * TESTING:
41 * Tested primarily with the controlspy Pager application.
42 * Susan Farley (susan@codeweavers.com)
43 *
44 * IMPLEMENTATION NOTES:
45 * This control uses WM_NCPAINT instead of WM_PAINT to paint itself
46 * as we need to scroll a child window. In order to do this we move
47 * the child window in the control's client area, using the clipping
48 * region that is automatically set around the client area. As the
49 * entire client area now consists of the child window, we must
50 * allocate space (WM_NCCALCSIZE) for the buttons and draw them as
51 * a non-client area (WM_NCPAINT).
52 * Robert Shearman <rob@codeweavers.com>
53 */
54
55 #include "comctl32.h"
56
57 WINE_DEFAULT_DEBUG_CHANNEL(pager);
58
59 typedef struct
60 {
61 HWND hwndSelf; /* handle of the control wnd */
62 HWND hwndChild; /* handle of the contained wnd */
63 HWND hwndNotify; /* handle of the parent wnd */
64 DWORD dwStyle; /* styles for this control */
65 COLORREF clrBk; /* background color */
66 INT nBorder; /* border size for the control */
67 INT nButtonSize;/* size of the pager btns */
68 INT nPos; /* scroll position */
69 INT nWidth; /* from child wnd's response to PGN_CALCSIZE */
70 INT nHeight; /* from child wnd's response to PGN_CALCSIZE */
71 BOOL bForward; /* forward WM_MOUSEMOVE msgs to the contained wnd */
72 BOOL bCapture; /* we have captured the mouse */
73 INT TLbtnState; /* state of top or left btn */
74 INT BRbtnState; /* state of bottom or right btn */
75 INT direction; /* direction of the scroll, (e.g. PGF_SCROLLUP) */
76 } PAGER_INFO;
77
78 #define TIMERID1 1
79 #define TIMERID2 2
80 #define INITIAL_DELAY 500
81 #define REPEAT_DELAY 50
82
83 static void
84 PAGER_GetButtonRects(const PAGER_INFO* infoPtr, RECT* prcTopLeft, RECT* prcBottomRight, BOOL bClientCoords)
85 {
86 RECT rcWindow;
87 GetWindowRect (infoPtr->hwndSelf, &rcWindow);
88
89 if (bClientCoords)
90 MapWindowPoints( 0, infoPtr->hwndSelf, (POINT *)&rcWindow, 2 );
91 else
92 OffsetRect(&rcWindow, -rcWindow.left, -rcWindow.top);
93
94 *prcTopLeft = *prcBottomRight = rcWindow;
95 if (infoPtr->dwStyle & PGS_HORZ)
96 {
97 prcTopLeft->right = prcTopLeft->left + infoPtr->nButtonSize;
98 prcBottomRight->left = prcBottomRight->right - infoPtr->nButtonSize;
99 }
100 else
101 {
102 prcTopLeft->bottom = prcTopLeft->top + infoPtr->nButtonSize;
103 prcBottomRight->top = prcBottomRight->bottom - infoPtr->nButtonSize;
104 }
105 }
106
107 static void
108 PAGER_DrawButton(HDC hdc, COLORREF clrBk, RECT rc,
109 BOOL horz, BOOL topLeft, INT btnState)
110 {
111 UINT flags;
112
113 TRACE("rc = %s, btnState = %d\n", wine_dbgstr_rect(&rc), btnState);
114
115 if (btnState == PGF_INVISIBLE)
116 return;
117
118 if ((rc.right - rc.left <= 0) || (rc.bottom - rc.top <= 0))
119 return;
120
121 if (horz)
122 flags = topLeft ? DFCS_SCROLLLEFT : DFCS_SCROLLRIGHT;
123 else
124 flags = topLeft ? DFCS_SCROLLUP : DFCS_SCROLLDOWN;
125
126 switch (btnState)
127 {
128 case PGF_HOT:
129 break;
130 case PGF_NORMAL:
131 flags |= DFCS_FLAT;
132 break;
133 case PGF_DEPRESSED:
134 flags |= DFCS_PUSHED;
135 break;
136 case PGF_GRAYED:
137 flags |= DFCS_INACTIVE | DFCS_FLAT;
138 break;
139 }
140 DrawFrameControl( hdc, &rc, DFC_SCROLL, flags );
141 }
142
143 /* << PAGER_GetDropTarget >> */
144
145 static inline LRESULT
146 PAGER_ForwardMouse (PAGER_INFO* infoPtr, BOOL bFwd)
147 {
148 TRACE("[%p]\n", infoPtr->hwndSelf);
149
150 infoPtr->bForward = bFwd;
151
152 return 0;
153 }
154
155 static inline LRESULT
156 PAGER_GetButtonState (const PAGER_INFO* infoPtr, INT btn)
157 {
158 LRESULT btnState = PGF_INVISIBLE;
159 TRACE("[%p]\n", infoPtr->hwndSelf);
160
161 if (btn == PGB_TOPORLEFT)
162 btnState = infoPtr->TLbtnState;
163 else if (btn == PGB_BOTTOMORRIGHT)
164 btnState = infoPtr->BRbtnState;
165
166 return btnState;
167 }
168
169
170 static inline INT
171 PAGER_GetPos(const PAGER_INFO *infoPtr)
172 {
173 TRACE("[%p] returns %d\n", infoPtr->hwndSelf, infoPtr->nPos);
174 return infoPtr->nPos;
175 }
176
177 static inline INT
178 PAGER_GetButtonSize(const PAGER_INFO *infoPtr)
179 {
180 TRACE("[%p] returns %d\n", infoPtr->hwndSelf, infoPtr->nButtonSize);
181 return infoPtr->nButtonSize;
182 }
183
184 static inline INT
185 PAGER_GetBorder(const PAGER_INFO *infoPtr)
186 {
187 TRACE("[%p] returns %d\n", infoPtr->hwndSelf, infoPtr->nBorder);
188 return infoPtr->nBorder;
189 }
190
191 static inline COLORREF
192 PAGER_GetBkColor(const PAGER_INFO *infoPtr)
193 {
194 TRACE("[%p] returns %06x\n", infoPtr->hwndSelf, infoPtr->clrBk);
195 return infoPtr->clrBk;
196 }
197
198 static void
199 PAGER_CalcSize( PAGER_INFO *infoPtr )
200 {
201 NMPGCALCSIZE nmpgcs;
202 ZeroMemory (&nmpgcs, sizeof (NMPGCALCSIZE));
203 nmpgcs.hdr.hwndFrom = infoPtr->hwndSelf;
204 nmpgcs.hdr.idFrom = GetWindowLongPtrW (infoPtr->hwndSelf, GWLP_ID);
205 nmpgcs.hdr.code = PGN_CALCSIZE;
206 nmpgcs.dwFlag = (infoPtr->dwStyle & PGS_HORZ) ? PGF_CALCWIDTH : PGF_CALCHEIGHT;
207 nmpgcs.iWidth = infoPtr->nWidth;
208 nmpgcs.iHeight = infoPtr->nHeight;
209 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY, nmpgcs.hdr.idFrom, (LPARAM)&nmpgcs);
210
211 if (infoPtr->dwStyle & PGS_HORZ)
212 infoPtr->nWidth = nmpgcs.iWidth;
213 else
214 infoPtr->nHeight = nmpgcs.iHeight;
215
216 TRACE("[%p] PGN_CALCSIZE returns %dx%d\n", infoPtr->hwndSelf, nmpgcs.iWidth, nmpgcs.iHeight );
217 }
218
219 static void
220 PAGER_PositionChildWnd(PAGER_INFO* infoPtr)
221 {
222 if (infoPtr->hwndChild)
223 {
224 RECT rcClient;
225 int nPos = infoPtr->nPos;
226
227 /* compensate for a grayed btn, which will soon become invisible */
228 if (infoPtr->TLbtnState == PGF_GRAYED)
229 nPos += infoPtr->nButtonSize;
230
231 GetClientRect(infoPtr->hwndSelf, &rcClient);
232
233 if (infoPtr->dwStyle & PGS_HORZ)
234 {
235 int wndSize = max(0, rcClient.right - rcClient.left);
236 if (infoPtr->nWidth < wndSize)
237 infoPtr->nWidth = wndSize;
238
239 TRACE("[%p] SWP %dx%d at (%d,%d)\n", infoPtr->hwndSelf,
240 infoPtr->nWidth, infoPtr->nHeight,
241 -nPos, 0);
242 SetWindowPos(infoPtr->hwndChild, 0,
243 -nPos, 0,
244 infoPtr->nWidth, infoPtr->nHeight,
245 SWP_NOZORDER);
246 }
247 else
248 {
249 int wndSize = max(0, rcClient.bottom - rcClient.top);
250 if (infoPtr->nHeight < wndSize)
251 infoPtr->nHeight = wndSize;
252
253 TRACE("[%p] SWP %dx%d at (%d,%d)\n", infoPtr->hwndSelf,
254 infoPtr->nWidth, infoPtr->nHeight,
255 0, -nPos);
256 SetWindowPos(infoPtr->hwndChild, 0,
257 0, -nPos,
258 infoPtr->nWidth, infoPtr->nHeight,
259 SWP_NOZORDER);
260 }
261
262 InvalidateRect(infoPtr->hwndChild, NULL, TRUE);
263 }
264 }
265
266 static INT
267 PAGER_GetScrollRange(PAGER_INFO* infoPtr)
268 {
269 INT scrollRange = 0;
270
271 if (infoPtr->hwndChild)
272 {
273 INT wndSize, childSize;
274 RECT wndRect;
275 GetWindowRect(infoPtr->hwndSelf, &wndRect);
276
277 PAGER_CalcSize(infoPtr);
278 if (infoPtr->dwStyle & PGS_HORZ)
279 {
280 wndSize = wndRect.right - wndRect.left;
281 childSize = infoPtr->nWidth;
282 }
283 else
284 {
285 wndSize = wndRect.bottom - wndRect.top;
286 childSize = infoPtr->nHeight;
287 }
288
289 TRACE("childSize = %d, wndSize = %d\n", childSize, wndSize);
290 if (childSize > wndSize)
291 scrollRange = childSize - wndSize + infoPtr->nButtonSize;
292 }
293
294 TRACE("[%p] returns %d\n", infoPtr->hwndSelf, scrollRange);
295 return scrollRange;
296 }
297
298 static void
299 PAGER_UpdateBtns(PAGER_INFO *infoPtr, INT scrollRange, BOOL hideGrayBtns)
300 {
301 BOOL resizeClient;
302 BOOL repaintBtns;
303 INT oldTLbtnState = infoPtr->TLbtnState;
304 INT oldBRbtnState = infoPtr->BRbtnState;
305 POINT pt;
306 RECT rcTopLeft, rcBottomRight;
307
308 /* get button rects */
309 PAGER_GetButtonRects(infoPtr, &rcTopLeft, &rcBottomRight, TRUE);
310
311 GetCursorPos(&pt);
312 ScreenToClient( infoPtr->hwndSelf, &pt );
313
314 /* update states based on scroll position */
315 if (infoPtr->nPos > 0)
316 {
317 if (infoPtr->TLbtnState == PGF_INVISIBLE || infoPtr->TLbtnState == PGF_GRAYED)
318 infoPtr->TLbtnState = PGF_NORMAL;
319 }
320 else if (!hideGrayBtns && PtInRect(&rcTopLeft, pt))
321 infoPtr->TLbtnState = PGF_GRAYED;
322 else
323 infoPtr->TLbtnState = PGF_INVISIBLE;
324
325 if (scrollRange <= 0)
326 {
327 infoPtr->TLbtnState = PGF_INVISIBLE;
328 infoPtr->BRbtnState = PGF_INVISIBLE;
329 }
330 else if (infoPtr->nPos < scrollRange)
331 {
332 if (infoPtr->BRbtnState == PGF_INVISIBLE || infoPtr->BRbtnState == PGF_GRAYED)
333 infoPtr->BRbtnState = PGF_NORMAL;
334 }
335 else if (!hideGrayBtns && PtInRect(&rcBottomRight, pt))
336 infoPtr->BRbtnState = PGF_GRAYED;
337 else
338 infoPtr->BRbtnState = PGF_INVISIBLE;
339
340 /* only need to resize when entering or leaving PGF_INVISIBLE state */
341 resizeClient =
342 ((oldTLbtnState == PGF_INVISIBLE) != (infoPtr->TLbtnState == PGF_INVISIBLE)) ||
343 ((oldBRbtnState == PGF_INVISIBLE) != (infoPtr->BRbtnState == PGF_INVISIBLE));
344 /* initiate NCCalcSize to resize client wnd if necessary */
345 if (resizeClient)
346 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
347 SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
348 SWP_NOZORDER | SWP_NOACTIVATE);
349
350 /* repaint when changing any state */
351 repaintBtns = (oldTLbtnState != infoPtr->TLbtnState) ||
352 (oldBRbtnState != infoPtr->BRbtnState);
353 if (repaintBtns)
354 SendMessageW(infoPtr->hwndSelf, WM_NCPAINT, 0, 0);
355 }
356
357 static LRESULT
358 PAGER_SetPos(PAGER_INFO* infoPtr, INT newPos, BOOL fromBtnPress)
359 {
360 INT scrollRange = PAGER_GetScrollRange(infoPtr);
361 INT oldPos = infoPtr->nPos;
362
363 if ((scrollRange <= 0) || (newPos < 0))
364 infoPtr->nPos = 0;
365 else if (newPos > scrollRange)
366 infoPtr->nPos = scrollRange;
367 else
368 infoPtr->nPos = newPos;
369
370 TRACE("[%p] pos=%d, oldpos=%d\n", infoPtr->hwndSelf, infoPtr->nPos, oldPos);
371
372 if (infoPtr->nPos != oldPos)
373 {
374 /* gray and restore btns, and if from WM_SETPOS, hide the gray btns */
375 PAGER_UpdateBtns(infoPtr, scrollRange, !fromBtnPress);
376 PAGER_PositionChildWnd(infoPtr);
377 }
378
379 return 0;
380 }
381
382 static LRESULT
383 PAGER_WindowPosChanging(PAGER_INFO* infoPtr, WINDOWPOS *winpos)
384 {
385 if ((infoPtr->dwStyle & CCS_NORESIZE) && !(winpos->flags & SWP_NOSIZE))
386 {
387 /* don't let the app resize the nonscrollable dimension of a control
388 * that was created with CCS_NORESIZE style
389 * (i.e. height for a horizontal pager, or width for a vertical one) */
390
391 /* except if the current dimension is 0 and app is setting for
392 * first time, then save amount as dimension. - GA 8/01 */
393
394 if (infoPtr->dwStyle & PGS_HORZ)
395 if (!infoPtr->nHeight && winpos->cy)
396 infoPtr->nHeight = winpos->cy;
397 else
398 winpos->cy = infoPtr->nHeight;
399 else
400 if (!infoPtr->nWidth && winpos->cx)
401 infoPtr->nWidth = winpos->cx;
402 else
403 winpos->cx = infoPtr->nWidth;
404 return 0;
405 }
406
407 return DefWindowProcW (infoPtr->hwndSelf, WM_WINDOWPOSCHANGING, 0, (LPARAM)winpos);
408 }
409
410 /******************************************************************
411 * For the PGM_RECALCSIZE message (but not the other uses in *
412 * this module), the native control does only the following: *
413 * *
414 * if (some condition) *
415 * PostMessageW(hwnd, EM_FMTLINES, 0, 0); *
416 * return DefWindowProcW(hwnd, PGM_RECALCSIZE, 0, 0); *
417 * *
418 * When we figure out what the "some condition" is we will *
419 * implement that for the message processing. *
420 ******************************************************************/
421
422 static LRESULT
423 PAGER_RecalcSize(PAGER_INFO *infoPtr)
424 {
425 TRACE("[%p]\n", infoPtr->hwndSelf);
426
427 if (infoPtr->hwndChild)
428 {
429 INT scrollRange = PAGER_GetScrollRange(infoPtr);
430
431 if (scrollRange <= 0)
432 {
433 infoPtr->nPos = -1;
434 PAGER_SetPos(infoPtr, 0, FALSE);
435 }
436 else
437 PAGER_PositionChildWnd(infoPtr);
438 }
439
440 return 1;
441 }
442
443
444 static COLORREF
445 PAGER_SetBkColor (PAGER_INFO* infoPtr, COLORREF clrBk)
446 {
447 COLORREF clrTemp = infoPtr->clrBk;
448
449 infoPtr->clrBk = clrBk;
450 TRACE("[%p] %06x\n", infoPtr->hwndSelf, infoPtr->clrBk);
451
452 /* the native control seems to do things this way */
453 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
454 SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
455 SWP_NOZORDER | SWP_NOACTIVATE);
456
457 RedrawWindow(infoPtr->hwndSelf, 0, 0, RDW_ERASE | RDW_INVALIDATE);
458
459 return clrTemp;
460 }
461
462
463 static INT
464 PAGER_SetBorder (PAGER_INFO* infoPtr, INT iBorder)
465 {
466 INT nTemp = infoPtr->nBorder;
467
468 infoPtr->nBorder = iBorder;
469 TRACE("[%p] %d\n", infoPtr->hwndSelf, infoPtr->nBorder);
470
471 PAGER_RecalcSize(infoPtr);
472
473 return nTemp;
474 }
475
476
477 static INT
478 PAGER_SetButtonSize (PAGER_INFO* infoPtr, INT iButtonSize)
479 {
480 INT nTemp = infoPtr->nButtonSize;
481
482 infoPtr->nButtonSize = iButtonSize;
483 TRACE("[%p] %d\n", infoPtr->hwndSelf, infoPtr->nButtonSize);
484
485 PAGER_RecalcSize(infoPtr);
486
487 return nTemp;
488 }
489
490
491 static LRESULT
492 PAGER_SetChild (PAGER_INFO* infoPtr, HWND hwndChild)
493 {
494 infoPtr->hwndChild = IsWindow (hwndChild) ? hwndChild : 0;
495
496 if (infoPtr->hwndChild)
497 {
498 TRACE("[%p] hwndChild=%p\n", infoPtr->hwndSelf, infoPtr->hwndChild);
499
500 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
501 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
502
503 /* position child within the page scroller */
504 SetWindowPos(infoPtr->hwndChild, HWND_TOP,
505 0,0,0,0,
506 SWP_SHOWWINDOW | SWP_NOSIZE); /* native is 0 */
507
508 infoPtr->nPos = -1;
509 PAGER_SetPos(infoPtr, 0, FALSE);
510 }
511
512 return 0;
513 }
514
515 static void
516 PAGER_Scroll(PAGER_INFO* infoPtr, INT dir)
517 {
518 NMPGSCROLL nmpgScroll;
519 RECT rcWnd;
520
521 if (infoPtr->hwndChild)
522 {
523 ZeroMemory (&nmpgScroll, sizeof (NMPGSCROLL));
524 nmpgScroll.hdr.hwndFrom = infoPtr->hwndSelf;
525 nmpgScroll.hdr.idFrom = GetWindowLongPtrW (infoPtr->hwndSelf, GWLP_ID);
526 nmpgScroll.hdr.code = PGN_SCROLL;
527
528 GetWindowRect(infoPtr->hwndSelf, &rcWnd);
529 GetClientRect(infoPtr->hwndSelf, &nmpgScroll.rcParent);
530 nmpgScroll.iXpos = nmpgScroll.iYpos = 0;
531 nmpgScroll.iDir = dir;
532
533 if (infoPtr->dwStyle & PGS_HORZ)
534 {
535 nmpgScroll.iScroll = rcWnd.right - rcWnd.left;
536 nmpgScroll.iXpos = infoPtr->nPos;
537 }
538 else
539 {
540 nmpgScroll.iScroll = rcWnd.bottom - rcWnd.top;
541 nmpgScroll.iYpos = infoPtr->nPos;
542 }
543 nmpgScroll.iScroll -= 2*infoPtr->nButtonSize;
544
545 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY, nmpgScroll.hdr.idFrom, (LPARAM)&nmpgScroll);
546
547 TRACE("[%p] PGN_SCROLL returns iScroll=%d\n", infoPtr->hwndSelf, nmpgScroll.iScroll);
548
549 if (nmpgScroll.iScroll > 0)
550 {
551 infoPtr->direction = dir;
552
553 if (dir == PGF_SCROLLLEFT || dir == PGF_SCROLLUP)
554 PAGER_SetPos(infoPtr, infoPtr->nPos - nmpgScroll.iScroll, TRUE);
555 else
556 PAGER_SetPos(infoPtr, infoPtr->nPos + nmpgScroll.iScroll, TRUE);
557 }
558 else
559 infoPtr->direction = -1;
560 }
561 }
562
563 static LRESULT
564 PAGER_FmtLines(const PAGER_INFO *infoPtr)
565 {
566 /* initiate NCCalcSize to resize client wnd and get size */
567 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
568 SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
569 SWP_NOZORDER | SWP_NOACTIVATE);
570
571 SetWindowPos(infoPtr->hwndChild, 0,
572 0,0,infoPtr->nWidth,infoPtr->nHeight,
573 0);
574
575 return DefWindowProcW (infoPtr->hwndSelf, EM_FMTLINES, 0, 0);
576 }
577
578 static LRESULT
579 PAGER_Create (HWND hwnd, const CREATESTRUCTW *lpcs)
580 {
581 PAGER_INFO *infoPtr;
582
583 /* allocate memory for info structure */
584 infoPtr = Alloc (sizeof(PAGER_INFO));
585 if (!infoPtr) return -1;
586 SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
587
588 /* set default settings */
589 infoPtr->hwndSelf = hwnd;
590 infoPtr->hwndChild = NULL;
591 infoPtr->hwndNotify = lpcs->hwndParent;
592 infoPtr->dwStyle = lpcs->style;
593 infoPtr->clrBk = GetSysColor(COLOR_BTNFACE);
594 infoPtr->nBorder = 0;
595 infoPtr->nButtonSize = 12;
596 infoPtr->nPos = 0;
597 infoPtr->nWidth = 0;
598 infoPtr->nHeight = 0;
599 infoPtr->bForward = FALSE;
600 infoPtr->bCapture = FALSE;
601 infoPtr->TLbtnState = PGF_INVISIBLE;
602 infoPtr->BRbtnState = PGF_INVISIBLE;
603 infoPtr->direction = -1;
604
605 if (infoPtr->dwStyle & PGS_DRAGNDROP)
606 FIXME("[%p] Drag and Drop style is not implemented yet.\n", infoPtr->hwndSelf);
607
608 return 0;
609 }
610
611
612 static LRESULT
613 PAGER_Destroy (PAGER_INFO *infoPtr)
614 {
615 SetWindowLongPtrW (infoPtr->hwndSelf, 0, 0);
616 Free (infoPtr); /* free pager info data */
617 return 0;
618 }
619
620 static LRESULT
621 PAGER_NCCalcSize(PAGER_INFO* infoPtr, WPARAM wParam, LPRECT lpRect)
622 {
623 RECT rcChild, rcWindow;
624
625 /*
626 * lpRect points to a RECT struct. On entry, the struct
627 * contains the proposed wnd rectangle for the window.
628 * On exit, the struct should contain the screen
629 * coordinates of the corresponding window's client area.
630 */
631
632 DefWindowProcW (infoPtr->hwndSelf, WM_NCCALCSIZE, wParam, (LPARAM)lpRect);
633
634 TRACE("orig rect=%s\n", wine_dbgstr_rect(lpRect));
635
636 GetWindowRect (infoPtr->hwndChild, &rcChild);
637 MapWindowPoints (0, infoPtr->hwndSelf, (LPPOINT)&rcChild, 2); /* FIXME: RECT != 2 POINTS */
638 GetWindowRect (infoPtr->hwndSelf, &rcWindow);
639
640 infoPtr->nWidth = lpRect->right - lpRect->left;
641 infoPtr->nHeight = lpRect->bottom - lpRect->top;
642 PAGER_CalcSize( infoPtr );
643
644 if (infoPtr->dwStyle & PGS_HORZ)
645 {
646 if (infoPtr->TLbtnState && (lpRect->left + infoPtr->nButtonSize < lpRect->right))
647 lpRect->left += infoPtr->nButtonSize;
648 if (infoPtr->BRbtnState && (lpRect->right - infoPtr->nButtonSize > lpRect->left))
649 lpRect->right -= infoPtr->nButtonSize;
650 }
651 else
652 {
653 if (infoPtr->TLbtnState && (lpRect->top + infoPtr->nButtonSize < lpRect->bottom))
654 lpRect->top += infoPtr->nButtonSize;
655 if (infoPtr->BRbtnState && (lpRect->bottom - infoPtr->nButtonSize > lpRect->top))
656 lpRect->bottom -= infoPtr->nButtonSize;
657 }
658
659 TRACE("nPos=%d, nHeight=%d, window=%s\n",
660 infoPtr->nPos, infoPtr->nHeight,
661 wine_dbgstr_rect(&rcWindow));
662
663 TRACE("[%p] client rect set to %dx%d at (%d,%d) BtnState[%d,%d]\n",
664 infoPtr->hwndSelf, lpRect->right-lpRect->left, lpRect->bottom-lpRect->top,
665 lpRect->left, lpRect->top,
666 infoPtr->TLbtnState, infoPtr->BRbtnState);
667
668 return 0;
669 }
670
671 static LRESULT
672 PAGER_NCPaint (const PAGER_INFO* infoPtr, HRGN hRgn)
673 {
674 RECT rcBottomRight, rcTopLeft;
675 HDC hdc;
676
677 if (infoPtr->dwStyle & WS_MINIMIZE)
678 return 0;
679
680 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)hRgn, 0);
681
682 if (!(hdc = GetDCEx (infoPtr->hwndSelf, 0, DCX_USESTYLE | DCX_WINDOW)))
683 return 0;
684
685 PAGER_GetButtonRects(infoPtr, &rcTopLeft, &rcBottomRight, FALSE);
686
687 PAGER_DrawButton(hdc, infoPtr->clrBk, rcTopLeft,
688 infoPtr->dwStyle & PGS_HORZ, TRUE, infoPtr->TLbtnState);
689 PAGER_DrawButton(hdc, infoPtr->clrBk, rcBottomRight,
690 infoPtr->dwStyle & PGS_HORZ, FALSE, infoPtr->BRbtnState);
691
692 ReleaseDC( infoPtr->hwndSelf, hdc );
693 return 0;
694 }
695
696 static INT
697 PAGER_HitTest (const PAGER_INFO* infoPtr, const POINT * pt)
698 {
699 RECT clientRect, rcTopLeft, rcBottomRight;
700 POINT ptWindow;
701
702 GetClientRect (infoPtr->hwndSelf, &clientRect);
703
704 if (PtInRect(&clientRect, *pt))
705 {
706 TRACE("child\n");
707 return -1;
708 }
709
710 ptWindow = *pt;
711 PAGER_GetButtonRects(infoPtr, &rcTopLeft, &rcBottomRight, TRUE);
712
713 if ((infoPtr->TLbtnState != PGF_INVISIBLE) && PtInRect(&rcTopLeft, ptWindow))
714 {
715 TRACE("PGB_TOPORLEFT\n");
716 return PGB_TOPORLEFT;
717 }
718 else if ((infoPtr->BRbtnState != PGF_INVISIBLE) && PtInRect(&rcBottomRight, ptWindow))
719 {
720 TRACE("PGB_BOTTOMORRIGHT\n");
721 return PGB_BOTTOMORRIGHT;
722 }
723
724 TRACE("nowhere\n");
725 return -1;
726 }
727
728 static LRESULT
729 PAGER_NCHitTest (const PAGER_INFO* infoPtr, INT x, INT y)
730 {
731 POINT pt;
732 INT nHit;
733
734 pt.x = x;
735 pt.y = y;
736
737 ScreenToClient (infoPtr->hwndSelf, &pt);
738 nHit = PAGER_HitTest(infoPtr, &pt);
739
740 return (nHit < 0) ? HTTRANSPARENT : HTCLIENT;
741 }
742
743 static LRESULT
744 PAGER_MouseMove (PAGER_INFO* infoPtr, INT keys, INT x, INT y)
745 {
746 POINT clpt, pt;
747 RECT wnrect;
748 BOOL topLeft = FALSE;
749 INT btnstate = 0;
750 INT hit;
751 HDC hdc;
752
753 pt.x = x;
754 pt.y = y;
755
756 TRACE("[%p] to (%d,%d)\n", infoPtr->hwndSelf, x, y);
757 ClientToScreen(infoPtr->hwndSelf, &pt);
758 GetWindowRect(infoPtr->hwndSelf, &wnrect);
759 if (PtInRect(&wnrect, pt)) {
760 RECT topleft, bottomright, *rect = NULL;
761
762 PAGER_GetButtonRects(infoPtr, &topleft, &bottomright, FALSE);
763
764 clpt = pt;
765 MapWindowPoints(0, infoPtr->hwndSelf, &clpt, 1);
766 hit = PAGER_HitTest(infoPtr, &clpt);
767 if ((hit == PGB_TOPORLEFT) && (infoPtr->TLbtnState == PGF_NORMAL))
768 {
769 topLeft = TRUE;
770 rect = &topleft;
771 infoPtr->TLbtnState = PGF_HOT;
772 btnstate = infoPtr->TLbtnState;
773 }
774 else if ((hit == PGB_BOTTOMORRIGHT) && (infoPtr->BRbtnState == PGF_NORMAL))
775 {
776 topLeft = FALSE;
777 rect = &bottomright;
778 infoPtr->BRbtnState = PGF_HOT;
779 btnstate = infoPtr->BRbtnState;
780 }
781
782 /* If in one of the buttons the capture and draw buttons */
783 if (rect)
784 {
785 TRACE("[%p] draw btn (%s), Capture %s, style %08x\n",
786 infoPtr->hwndSelf, wine_dbgstr_rect(rect),
787 (infoPtr->bCapture) ? "TRUE" : "FALSE",
788 infoPtr->dwStyle);
789 if (!infoPtr->bCapture)
790 {
791 TRACE("[%p] SetCapture\n", infoPtr->hwndSelf);
792 SetCapture(infoPtr->hwndSelf);
793 infoPtr->bCapture = TRUE;
794 }
795 if (infoPtr->dwStyle & PGS_AUTOSCROLL)
796 SetTimer(infoPtr->hwndSelf, TIMERID1, 0x3e, 0);
797 hdc = GetWindowDC(infoPtr->hwndSelf);
798 /* OffsetRect(wnrect, 0 | 1, 0 | 1) */
799 PAGER_DrawButton(hdc, infoPtr->clrBk, *rect,
800 infoPtr->dwStyle & PGS_HORZ, topLeft, btnstate);
801 ReleaseDC(infoPtr->hwndSelf, hdc);
802 return 0;
803 }
804 }
805
806 /* If we think we are captured, then do release */
807 if (infoPtr->bCapture && (WindowFromPoint(pt) != infoPtr->hwndSelf))
808 {
809 NMHDR nmhdr;
810
811 infoPtr->bCapture = FALSE;
812
813 if (GetCapture() == infoPtr->hwndSelf)
814 {
815 ReleaseCapture();
816
817 if (infoPtr->TLbtnState == PGF_GRAYED)
818 {
819 infoPtr->TLbtnState = PGF_INVISIBLE;
820 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
821 SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
822 SWP_NOZORDER | SWP_NOACTIVATE);
823 }
824 else if (infoPtr->TLbtnState == PGF_HOT)
825 {
826 infoPtr->TLbtnState = PGF_NORMAL;
827 /* FIXME: just invalidate button rect */
828 RedrawWindow(infoPtr->hwndSelf, NULL, NULL, RDW_FRAME | RDW_INVALIDATE);
829 }
830
831 if (infoPtr->BRbtnState == PGF_GRAYED)
832 {
833 infoPtr->BRbtnState = PGF_INVISIBLE;
834 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
835 SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
836 SWP_NOZORDER | SWP_NOACTIVATE);
837 }
838 else if (infoPtr->BRbtnState == PGF_HOT)
839 {
840 infoPtr->BRbtnState = PGF_NORMAL;
841 /* FIXME: just invalidate button rect */
842 RedrawWindow(infoPtr->hwndSelf, NULL, NULL, RDW_FRAME | RDW_INVALIDATE);
843 }
844
845 /* Notify parent of released mouse capture */
846 memset(&nmhdr, 0, sizeof(NMHDR));
847 nmhdr.hwndFrom = infoPtr->hwndSelf;
848 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
849 nmhdr.code = NM_RELEASEDCAPTURE;
850 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmhdr.idFrom, (LPARAM)&nmhdr);
851 }
852 if (IsWindow(infoPtr->hwndSelf))
853 KillTimer(infoPtr->hwndSelf, TIMERID1);
854 }
855 return 0;
856 }
857
858 static LRESULT
859 PAGER_LButtonDown (PAGER_INFO* infoPtr, INT keys, INT x, INT y)
860 {
861 BOOL repaintBtns = FALSE;
862 POINT pt;
863 INT hit;
864
865 pt.x = x;
866 pt.y = y;
867
868 TRACE("[%p] at (%d,%d)\n", infoPtr->hwndSelf, x, y);
869
870 hit = PAGER_HitTest(infoPtr, &pt);
871
872 /* put btn in DEPRESSED state */
873 if (hit == PGB_TOPORLEFT)
874 {
875 repaintBtns = infoPtr->TLbtnState != PGF_DEPRESSED;
876 infoPtr->TLbtnState = PGF_DEPRESSED;
877 SetTimer(infoPtr->hwndSelf, TIMERID1, INITIAL_DELAY, 0);
878 }
879 else if (hit == PGB_BOTTOMORRIGHT)
880 {
881 repaintBtns = infoPtr->BRbtnState != PGF_DEPRESSED;
882 infoPtr->BRbtnState = PGF_DEPRESSED;
883 SetTimer(infoPtr->hwndSelf, TIMERID1, INITIAL_DELAY, 0);
884 }
885
886 if (repaintBtns)
887 SendMessageW(infoPtr->hwndSelf, WM_NCPAINT, 0, 0);
888
889 switch(hit)
890 {
891 case PGB_TOPORLEFT:
892 if (infoPtr->dwStyle & PGS_HORZ)
893 {
894 TRACE("[%p] PGF_SCROLLLEFT\n", infoPtr->hwndSelf);
895 PAGER_Scroll(infoPtr, PGF_SCROLLLEFT);
896 }
897 else
898 {
899 TRACE("[%p] PGF_SCROLLUP\n", infoPtr->hwndSelf);
900 PAGER_Scroll(infoPtr, PGF_SCROLLUP);
901 }
902 break;
903 case PGB_BOTTOMORRIGHT:
904 if (infoPtr->dwStyle & PGS_HORZ)
905 {
906 TRACE("[%p] PGF_SCROLLRIGHT\n", infoPtr->hwndSelf);
907 PAGER_Scroll(infoPtr, PGF_SCROLLRIGHT);
908 }
909 else
910 {
911 TRACE("[%p] PGF_SCROLLDOWN\n", infoPtr->hwndSelf);
912 PAGER_Scroll(infoPtr, PGF_SCROLLDOWN);
913 }
914 break;
915 default:
916 break;
917 }
918
919 return 0;
920 }
921
922 static LRESULT
923 PAGER_LButtonUp (PAGER_INFO* infoPtr, INT keys, INT x, INT y)
924 {
925 TRACE("[%p]\n", infoPtr->hwndSelf);
926
927 KillTimer (infoPtr->hwndSelf, TIMERID1);
928 KillTimer (infoPtr->hwndSelf, TIMERID2);
929
930 /* make PRESSED btns NORMAL but don't hide gray btns */
931 if (infoPtr->TLbtnState & (PGF_HOT | PGF_DEPRESSED))
932 infoPtr->TLbtnState = PGF_NORMAL;
933 if (infoPtr->BRbtnState & (PGF_HOT | PGF_DEPRESSED))
934 infoPtr->BRbtnState = PGF_NORMAL;
935
936 return 0;
937 }
938
939 static LRESULT
940 PAGER_Timer (PAGER_INFO* infoPtr, INT nTimerId)
941 {
942 INT dir;
943
944 /* if initial timer, kill it and start the repeat timer */
945 if (nTimerId == TIMERID1) {
946 if (infoPtr->TLbtnState == PGF_HOT)
947 dir = (infoPtr->dwStyle & PGS_HORZ) ?
948 PGF_SCROLLLEFT : PGF_SCROLLUP;
949 else
950 dir = (infoPtr->dwStyle & PGS_HORZ) ?
951 PGF_SCROLLRIGHT : PGF_SCROLLDOWN;
952 TRACE("[%p] TIMERID1: style=%08x, dir=%d\n",
953 infoPtr->hwndSelf, infoPtr->dwStyle, dir);
954 KillTimer(infoPtr->hwndSelf, TIMERID1);
955 SetTimer(infoPtr->hwndSelf, TIMERID1, REPEAT_DELAY, 0);
956 if (infoPtr->dwStyle & PGS_AUTOSCROLL) {
957 PAGER_Scroll(infoPtr, dir);
958 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
959 SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
960 SWP_NOZORDER | SWP_NOACTIVATE);
961 }
962 return 0;
963
964 }
965
966 TRACE("[%p] TIMERID2: dir=%d\n", infoPtr->hwndSelf, infoPtr->direction);
967 KillTimer(infoPtr->hwndSelf, TIMERID2);
968 if (infoPtr->direction > 0) {
969 PAGER_Scroll(infoPtr, infoPtr->direction);
970 SetTimer(infoPtr->hwndSelf, TIMERID2, REPEAT_DELAY, 0);
971 }
972 return 0;
973 }
974
975 static LRESULT
976 PAGER_EraseBackground (const PAGER_INFO* infoPtr, HDC hdc)
977 {
978 POINT pt, ptorig;
979 HWND parent;
980 LRESULT ret;
981
982 pt.x = 0;
983 pt.y = 0;
984 parent = GetParent(infoPtr->hwndSelf);
985 MapWindowPoints(infoPtr->hwndSelf, parent, &pt, 1);
986 OffsetWindowOrgEx (hdc, pt.x, pt.y, &ptorig);
987 ret = SendMessageW (parent, WM_ERASEBKGND, (WPARAM)hdc, 0);
988 SetWindowOrgEx (hdc, ptorig.x, ptorig.y, 0);
989
990 return ret;
991 }
992
993
994 static LRESULT
995 PAGER_Size (PAGER_INFO* infoPtr, INT type, INT x, INT y)
996 {
997 /* note that WM_SIZE is sent whenever NCCalcSize resizes the client wnd */
998
999 TRACE("[%p] %d,%d\n", infoPtr->hwndSelf, x, y);
1000
1001 if (infoPtr->dwStyle & PGS_HORZ)
1002 infoPtr->nHeight = y;
1003 else
1004 infoPtr->nWidth = x;
1005
1006 return PAGER_RecalcSize(infoPtr);
1007 }
1008
1009
1010 static LRESULT
1011 PAGER_StyleChanged(PAGER_INFO *infoPtr, WPARAM wStyleType, const STYLESTRUCT *lpss)
1012 {
1013 DWORD oldStyle = infoPtr->dwStyle;
1014
1015 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
1016 wStyleType, lpss->styleOld, lpss->styleNew);
1017
1018 if (wStyleType != GWL_STYLE) return 0;
1019
1020 infoPtr->dwStyle = lpss->styleNew;
1021
1022 if ((oldStyle ^ lpss->styleNew) & (PGS_HORZ | PGS_VERT))
1023 {
1024 PAGER_RecalcSize(infoPtr);
1025 }
1026
1027 return 0;
1028 }
1029
1030 static LRESULT WINAPI
1031 PAGER_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1032 {
1033 PAGER_INFO *infoPtr = (PAGER_INFO *)GetWindowLongPtrW(hwnd, 0);
1034
1035 if (!infoPtr && (uMsg != WM_CREATE))
1036 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
1037
1038 switch (uMsg)
1039 {
1040 case EM_FMTLINES:
1041 return PAGER_FmtLines(infoPtr);
1042
1043 case PGM_FORWARDMOUSE:
1044 return PAGER_ForwardMouse (infoPtr, (BOOL)wParam);
1045
1046 case PGM_GETBKCOLOR:
1047 return PAGER_GetBkColor(infoPtr);
1048
1049 case PGM_GETBORDER:
1050 return PAGER_GetBorder(infoPtr);
1051
1052 case PGM_GETBUTTONSIZE:
1053 return PAGER_GetButtonSize(infoPtr);
1054
1055 case PGM_GETPOS:
1056 return PAGER_GetPos(infoPtr);
1057
1058 case PGM_GETBUTTONSTATE:
1059 return PAGER_GetButtonState (infoPtr, (INT)lParam);
1060
1061 /* case PGM_GETDROPTARGET: */
1062
1063 case PGM_RECALCSIZE:
1064 return PAGER_RecalcSize(infoPtr);
1065
1066 case PGM_SETBKCOLOR:
1067 return PAGER_SetBkColor (infoPtr, (COLORREF)lParam);
1068
1069 case PGM_SETBORDER:
1070 return PAGER_SetBorder (infoPtr, (INT)lParam);
1071
1072 case PGM_SETBUTTONSIZE:
1073 return PAGER_SetButtonSize (infoPtr, (INT)lParam);
1074
1075 case PGM_SETCHILD:
1076 return PAGER_SetChild (infoPtr, (HWND)lParam);
1077
1078 case PGM_SETPOS:
1079 return PAGER_SetPos(infoPtr, (INT)lParam, FALSE);
1080
1081 case WM_CREATE:
1082 return PAGER_Create (hwnd, (LPCREATESTRUCTW)lParam);
1083
1084 case WM_DESTROY:
1085 return PAGER_Destroy (infoPtr);
1086
1087 case WM_SIZE:
1088 return PAGER_Size (infoPtr, (INT)wParam, (short)LOWORD(lParam), (short)HIWORD(lParam));
1089
1090 case WM_NCPAINT:
1091 return PAGER_NCPaint (infoPtr, (HRGN)wParam);
1092
1093 case WM_WINDOWPOSCHANGING:
1094 return PAGER_WindowPosChanging (infoPtr, (WINDOWPOS*)lParam);
1095
1096 case WM_STYLECHANGED:
1097 return PAGER_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
1098
1099 case WM_NCCALCSIZE:
1100 return PAGER_NCCalcSize (infoPtr, wParam, (LPRECT)lParam);
1101
1102 case WM_NCHITTEST:
1103 return PAGER_NCHitTest (infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
1104
1105 case WM_MOUSEMOVE:
1106 if (infoPtr->bForward && infoPtr->hwndChild)
1107 PostMessageW(infoPtr->hwndChild, WM_MOUSEMOVE, wParam, lParam);
1108 return PAGER_MouseMove (infoPtr, (INT)wParam, (short)LOWORD(lParam), (short)HIWORD(lParam));
1109
1110 case WM_LBUTTONDOWN:
1111 return PAGER_LButtonDown (infoPtr, (INT)wParam, (short)LOWORD(lParam), (short)HIWORD(lParam));
1112
1113 case WM_LBUTTONUP:
1114 return PAGER_LButtonUp (infoPtr, (INT)wParam, (short)LOWORD(lParam), (short)HIWORD(lParam));
1115
1116 case WM_ERASEBKGND:
1117 return PAGER_EraseBackground (infoPtr, (HDC)wParam);
1118
1119 case WM_TIMER:
1120 return PAGER_Timer (infoPtr, (INT)wParam);
1121
1122 case WM_NOTIFY:
1123 case WM_COMMAND:
1124 return SendMessageW (infoPtr->hwndNotify, uMsg, wParam, lParam);
1125
1126 default:
1127 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
1128 }
1129 }
1130
1131
1132 VOID
1133 PAGER_Register (void)
1134 {
1135 WNDCLASSW wndClass;
1136
1137 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
1138 wndClass.style = CS_GLOBALCLASS;
1139 wndClass.lpfnWndProc = PAGER_WindowProc;
1140 wndClass.cbClsExtra = 0;
1141 wndClass.cbWndExtra = sizeof(PAGER_INFO *);
1142 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
1143 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
1144 wndClass.lpszClassName = WC_PAGESCROLLERW;
1145
1146 RegisterClassW (&wndClass);
1147 }
1148
1149
1150 VOID
1151 PAGER_Unregister (void)
1152 {
1153 UnregisterClassW (WC_PAGESCROLLERW, NULL);
1154 }