comctl32: Don't change month range in MCM_SETRANGE. Add tests for this.
[wine] / dlls / 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 <stdarg.h>
56 #include <string.h>
57 #include "windef.h"
58 #include "winbase.h"
59 #include "wingdi.h"
60 #include "winuser.h"
61 #include "winnls.h"
62 #include "windowsx.h"
63 #include "commctrl.h"
64 #include "comctl32.h"
65 #include "wine/debug.h"
66
67 WINE_DEFAULT_DEBUG_CHANNEL(pager);
68
69 typedef struct
70 {
71     HWND   hwndSelf;   /* handle of the control wnd */
72     HWND   hwndChild;  /* handle of the contained wnd */
73     HWND   hwndNotify; /* handle of the parent wnd */
74     DWORD  dwStyle;    /* styles for this control */
75     COLORREF clrBk;    /* background color */
76     INT    nBorder;    /* border size for the control */
77     INT    nButtonSize;/* size of the pager btns */
78     INT    nPos;       /* scroll position */
79     INT    nWidth;     /* from child wnd's response to PGN_CALCSIZE */
80     INT    nHeight;    /* from child wnd's response to PGN_CALCSIZE */
81     BOOL   bForward;   /* forward WM_MOUSEMOVE msgs to the contained wnd */
82     BOOL   bCapture;   /* we have captured the mouse  */
83     INT    TLbtnState; /* state of top or left btn */
84     INT    BRbtnState; /* state of bottom or right btn */
85     INT    direction;  /* direction of the scroll, (e.g. PGF_SCROLLUP) */
86 } PAGER_INFO;
87
88 #define MIN_ARROW_WIDTH  8
89 #define MIN_ARROW_HEIGHT 5
90
91 #define TIMERID1         1
92 #define TIMERID2         2
93 #define INITIAL_DELAY    500
94 #define REPEAT_DELAY     50
95
96 static void
97 PAGER_GetButtonRects(PAGER_INFO* infoPtr, RECT* prcTopLeft, RECT* prcBottomRight, BOOL bClientCoords)
98 {
99     RECT rcWindow;
100     GetWindowRect (infoPtr->hwndSelf, &rcWindow);
101
102     if (bClientCoords)
103     {
104         POINT pt = {rcWindow.left, rcWindow.top};
105         ScreenToClient(infoPtr->hwndSelf, &pt);
106         OffsetRect(&rcWindow, -(rcWindow.left-pt.x), -(rcWindow.top-pt.y));
107     }
108     else
109         OffsetRect(&rcWindow, -rcWindow.left, -rcWindow.top);
110
111     *prcTopLeft = *prcBottomRight = rcWindow;
112     if (infoPtr->dwStyle & PGS_HORZ)
113     {
114         prcTopLeft->right = prcTopLeft->left + infoPtr->nButtonSize;
115         prcBottomRight->left = prcBottomRight->right - infoPtr->nButtonSize;
116     }
117     else
118     {
119         prcTopLeft->bottom = prcTopLeft->top + infoPtr->nButtonSize;
120         prcBottomRight->top = prcBottomRight->bottom - infoPtr->nButtonSize;
121     }
122 }
123
124 /* the horizontal arrows are:
125  *
126  * 01234    01234
127  * 1  *      *
128  * 2 **      **
129  * 3***      ***
130  * 4***      ***
131  * 5 **      **
132  * 6  *      *
133  * 7
134  *
135  */
136 static void
137 PAGER_DrawHorzArrow (HDC hdc, RECT r, INT colorRef, BOOL left)
138 {
139     INT x, y, w, h;
140     HPEN hPen, hOldPen;
141
142     w = r.right - r.left + 1;
143     h = r.bottom - r.top + 1;
144     if ((h < MIN_ARROW_WIDTH) || (w < MIN_ARROW_HEIGHT))
145         return;  /* refuse to draw partial arrow */
146
147     if (!(hPen = CreatePen( PS_SOLID, 1, GetSysColor( colorRef )))) return;
148     hOldPen = SelectObject ( hdc, hPen );
149     if (left)
150     {
151         x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 3;
152         y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
153         MoveToEx (hdc, x, y, NULL);
154         LineTo (hdc, x--, y+5); y++;
155         MoveToEx (hdc, x, y, NULL);
156         LineTo (hdc, x--, y+3); y++;
157         MoveToEx (hdc, x, y, NULL);
158         LineTo (hdc, x, y+1);
159     }
160     else
161     {
162         x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
163         y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
164         MoveToEx (hdc, x, y, NULL);
165         LineTo (hdc, x++, y+5); y++;
166         MoveToEx (hdc, x, y, NULL);
167         LineTo (hdc, x++, y+3); y++;
168         MoveToEx (hdc, x, y, NULL);
169         LineTo (hdc, x, y+1);
170     }
171
172     SelectObject( hdc, hOldPen );
173     DeleteObject( hPen );
174 }
175
176 /* the vertical arrows are:
177  *
178  * 01234567    01234567
179  * 1******        **
180  * 2 ****        ****
181  * 3  **        ******
182  * 4
183  *
184  */
185 static void
186 PAGER_DrawVertArrow (HDC hdc, RECT r, INT colorRef, BOOL up)
187 {
188     INT x, y, w, h;
189     HPEN hPen, hOldPen;
190
191     w = r.right - r.left + 1;
192     h = r.bottom - r.top + 1;
193     if ((h < MIN_ARROW_WIDTH) || (w < MIN_ARROW_HEIGHT))
194         return;  /* refuse to draw partial arrow */
195
196     if (!(hPen = CreatePen( PS_SOLID, 1, GetSysColor( colorRef )))) return;
197     hOldPen = SelectObject ( hdc, hPen );
198     if (up)
199     {
200         x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
201         y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 3;
202         MoveToEx (hdc, x, y, NULL);
203         LineTo (hdc, x+5, y--); x++;
204         MoveToEx (hdc, x, y, NULL);
205         LineTo (hdc, x+3, y--); x++;
206         MoveToEx (hdc, x, y, NULL);
207         LineTo (hdc, x+1, y);
208     }
209     else
210     {
211         x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
212         y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
213         MoveToEx (hdc, x, y, NULL);
214         LineTo (hdc, x+5, y++); x++;
215         MoveToEx (hdc, x, y, NULL);
216         LineTo (hdc, x+3, y++); x++;
217         MoveToEx (hdc, x, y, NULL);
218         LineTo (hdc, x+1, y);
219     }
220
221     SelectObject( hdc, hOldPen );
222     DeleteObject( hPen );
223 }
224
225 static void
226 PAGER_DrawButton(HDC hdc, COLORREF clrBk, RECT arrowRect,
227                  BOOL horz, BOOL topLeft, INT btnState)
228 {
229     HBRUSH   hBrush, hOldBrush;
230     RECT     rc = arrowRect;
231
232     TRACE("arrowRect = %s, btnState = %d\n", wine_dbgstr_rect(&arrowRect), btnState);
233
234     if (btnState == PGF_INVISIBLE)
235         return;
236
237     if ((rc.right - rc.left <= 0) || (rc.bottom - rc.top <= 0))
238         return;
239
240     hBrush = CreateSolidBrush(clrBk);
241     hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
242
243     FillRect(hdc, &rc, hBrush);
244
245     if (btnState == PGF_HOT)
246     {
247        DrawEdge( hdc, &rc, BDR_RAISEDINNER, BF_RECT);
248        if (horz)
249            PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
250        else
251            PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
252     }
253     else if (btnState == PGF_NORMAL)
254     {
255        DrawEdge (hdc, &rc, BDR_OUTER, BF_FLAT);
256        if (horz)
257            PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
258        else
259            PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
260     }
261     else if (btnState == PGF_DEPRESSED)
262     {
263        DrawEdge( hdc, &rc, BDR_SUNKENOUTER, BF_RECT);
264        if (horz)
265            PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
266        else
267            PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
268     }
269     else if (btnState == PGF_GRAYED)
270     {
271        DrawEdge (hdc, &rc, BDR_OUTER, BF_FLAT);
272        if (horz)
273        {
274            PAGER_DrawHorzArrow(hdc, rc, COLOR_3DHIGHLIGHT, topLeft);
275            rc.left++, rc.top++; rc.right++, rc.bottom++;
276            PAGER_DrawHorzArrow(hdc, rc, COLOR_3DSHADOW, topLeft);
277        }
278        else
279        {
280            PAGER_DrawVertArrow(hdc, rc, COLOR_3DHIGHLIGHT, topLeft);
281            rc.left++, rc.top++; rc.right++, rc.bottom++;
282            PAGER_DrawVertArrow(hdc, rc, COLOR_3DSHADOW, topLeft);
283        }
284     }
285
286     SelectObject( hdc, hOldBrush );
287     DeleteObject(hBrush);
288 }
289
290 /* << PAGER_GetDropTarget >> */
291
292 static inline LRESULT
293 PAGER_ForwardMouse (PAGER_INFO* infoPtr, BOOL bFwd)
294 {
295     TRACE("[%p]\n", infoPtr->hwndSelf);
296
297     infoPtr->bForward = bFwd;
298
299     return 0;
300 }
301
302 static inline LRESULT
303 PAGER_GetButtonState (PAGER_INFO* infoPtr, INT btn)
304 {
305     LRESULT btnState = PGF_INVISIBLE;
306     TRACE("[%p]\n", infoPtr->hwndSelf);
307
308     if (btn == PGB_TOPORLEFT)
309         btnState = infoPtr->TLbtnState;
310     else if (btn == PGB_BOTTOMORRIGHT)
311         btnState = infoPtr->BRbtnState;
312
313     return btnState;
314 }
315
316
317 static inline INT
318 PAGER_GetPos(PAGER_INFO *infoPtr)
319 {
320     TRACE("[%p] returns %d\n", infoPtr->hwndSelf, infoPtr->nPos);
321     return infoPtr->nPos;
322 }
323
324 static inline INT
325 PAGER_GetButtonSize(PAGER_INFO *infoPtr)
326 {
327     TRACE("[%p] returns %d\n", infoPtr->hwndSelf, infoPtr->nButtonSize);
328     return infoPtr->nButtonSize;
329 }
330
331 static inline INT
332 PAGER_GetBorder(PAGER_INFO *infoPtr)
333 {
334     TRACE("[%p] returns %d\n", infoPtr->hwndSelf, infoPtr->nBorder);
335     return infoPtr->nBorder;
336 }
337
338 static inline COLORREF
339 PAGER_GetBkColor(PAGER_INFO *infoPtr)
340 {
341     TRACE("[%p] returns %06lx\n", infoPtr->hwndSelf, infoPtr->clrBk);
342     return infoPtr->clrBk;
343 }
344
345 static void
346 PAGER_CalcSize (PAGER_INFO *infoPtr, INT* size, BOOL getWidth)
347 {
348     NMPGCALCSIZE nmpgcs;
349     ZeroMemory (&nmpgcs, sizeof (NMPGCALCSIZE));
350     nmpgcs.hdr.hwndFrom = infoPtr->hwndSelf;
351     nmpgcs.hdr.idFrom   = GetWindowLongPtrW (infoPtr->hwndSelf, GWLP_ID);
352     nmpgcs.hdr.code = PGN_CALCSIZE;
353     nmpgcs.dwFlag = getWidth ? PGF_CALCWIDTH : PGF_CALCHEIGHT;
354     nmpgcs.iWidth = getWidth ? *size : 0;
355     nmpgcs.iHeight = getWidth ? 0 : *size;
356     SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
357                   (WPARAM)nmpgcs.hdr.idFrom, (LPARAM)&nmpgcs);
358
359     *size = getWidth ? nmpgcs.iWidth : nmpgcs.iHeight;
360
361     TRACE("[%p] PGN_CALCSIZE returns %s=%d\n", infoPtr->hwndSelf,
362                   getWidth ? "width" : "height", *size);
363 }
364
365 static void
366 PAGER_PositionChildWnd(PAGER_INFO* infoPtr)
367 {
368     if (infoPtr->hwndChild)
369     {
370         RECT rcClient;
371         int nPos = infoPtr->nPos;
372
373         /* compensate for a grayed btn, which will soon become invisible */
374         if (infoPtr->TLbtnState == PGF_GRAYED)
375             nPos += infoPtr->nButtonSize;
376
377         GetClientRect(infoPtr->hwndSelf, &rcClient);
378
379         if (infoPtr->dwStyle & PGS_HORZ)
380         {
381             int wndSize = max(0, rcClient.right - rcClient.left);
382             if (infoPtr->nWidth < wndSize)
383                 infoPtr->nWidth = wndSize;
384
385             TRACE("[%p] SWP %dx%d at (%d,%d)\n", infoPtr->hwndSelf,
386                          infoPtr->nWidth, infoPtr->nHeight,
387                          -nPos, 0);
388             SetWindowPos(infoPtr->hwndChild, 0,
389                          -nPos, 0,
390                          infoPtr->nWidth, infoPtr->nHeight,
391                          SWP_NOZORDER);
392         }
393         else
394         {
395             int wndSize = max(0, rcClient.bottom - rcClient.top);
396             if (infoPtr->nHeight < wndSize)
397                 infoPtr->nHeight = wndSize;
398
399             TRACE("[%p] SWP %dx%d at (%d,%d)\n", infoPtr->hwndSelf,
400                          infoPtr->nWidth, infoPtr->nHeight,
401                          0, -nPos);
402             SetWindowPos(infoPtr->hwndChild, 0,
403                          0, -nPos,
404                          infoPtr->nWidth, infoPtr->nHeight,
405                          SWP_NOZORDER);
406         }
407
408         InvalidateRect(infoPtr->hwndChild, NULL, TRUE);
409     }
410 }
411
412 static INT
413 PAGER_GetScrollRange(PAGER_INFO* infoPtr)
414 {
415     INT scrollRange = 0;
416
417     if (infoPtr->hwndChild)
418     {
419         INT wndSize, childSize;
420         RECT wndRect;
421         GetWindowRect(infoPtr->hwndSelf, &wndRect);
422
423         if (infoPtr->dwStyle & PGS_HORZ)
424         {
425             wndSize = wndRect.right - wndRect.left;
426             PAGER_CalcSize(infoPtr, &infoPtr->nWidth, TRUE);
427             childSize = infoPtr->nWidth;
428         }
429         else
430         {
431             wndSize = wndRect.bottom - wndRect.top;
432             PAGER_CalcSize(infoPtr, &infoPtr->nHeight, FALSE);
433             childSize = infoPtr->nHeight;
434         }
435
436         TRACE("childSize = %d,  wndSize = %d\n", childSize, wndSize);
437         if (childSize > wndSize)
438             scrollRange = childSize - wndSize + infoPtr->nButtonSize;
439     }
440
441     TRACE("[%p] returns %d\n", infoPtr->hwndSelf, scrollRange);
442     return scrollRange;
443 }
444
445 static void
446 PAGER_UpdateBtns(PAGER_INFO *infoPtr, INT scrollRange, BOOL hideGrayBtns)
447 {
448     BOOL resizeClient;
449     BOOL repaintBtns;
450     INT oldTLbtnState = infoPtr->TLbtnState;
451     INT oldBRbtnState = infoPtr->BRbtnState;
452     POINT pt;
453     RECT rcTopLeft, rcBottomRight;
454
455     /* get button rects */
456     PAGER_GetButtonRects(infoPtr, &rcTopLeft, &rcBottomRight, FALSE);
457
458     GetCursorPos(&pt);
459
460     /* update states based on scroll position */
461     if (infoPtr->nPos > 0)
462     {
463         if (infoPtr->TLbtnState == PGF_INVISIBLE || infoPtr->TLbtnState == PGF_GRAYED)
464             infoPtr->TLbtnState = PGF_NORMAL;
465     }
466     else if (PtInRect(&rcTopLeft, pt))
467         infoPtr->TLbtnState = PGF_GRAYED;
468     else
469         infoPtr->TLbtnState = PGF_INVISIBLE;
470
471     if (scrollRange <= 0)
472     {
473         infoPtr->TLbtnState = PGF_INVISIBLE;
474         infoPtr->BRbtnState = PGF_INVISIBLE;
475     }
476     else if (infoPtr->nPos < scrollRange)
477     {
478         if (infoPtr->BRbtnState == PGF_INVISIBLE || infoPtr->BRbtnState == PGF_GRAYED)
479             infoPtr->BRbtnState = PGF_NORMAL;
480     }
481     else if (PtInRect(&rcBottomRight, pt))
482         infoPtr->BRbtnState = PGF_GRAYED;
483     else
484         infoPtr->BRbtnState = PGF_INVISIBLE;
485
486     /* only need to resize when entering or leaving PGF_INVISIBLE state */
487     resizeClient =
488         ((oldTLbtnState == PGF_INVISIBLE) != (infoPtr->TLbtnState == PGF_INVISIBLE)) ||
489         ((oldBRbtnState == PGF_INVISIBLE) != (infoPtr->BRbtnState == PGF_INVISIBLE));
490     /* initiate NCCalcSize to resize client wnd if necessary */
491     if (resizeClient)
492         SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
493                      SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
494                      SWP_NOZORDER | SWP_NOACTIVATE);
495
496     /* repaint when changing any state */
497     repaintBtns = (oldTLbtnState != infoPtr->TLbtnState) || 
498                   (oldBRbtnState != infoPtr->BRbtnState);
499     if (repaintBtns)
500         SendMessageW(infoPtr->hwndSelf, WM_NCPAINT, 0, 0);
501 }
502
503 static LRESULT
504 PAGER_SetPos(PAGER_INFO* infoPtr, INT newPos, BOOL fromBtnPress)
505 {
506     INT scrollRange = PAGER_GetScrollRange(infoPtr);
507     INT oldPos = infoPtr->nPos;
508
509     if ((scrollRange <= 0) || (newPos < 0))
510         infoPtr->nPos = 0;
511     else if (newPos > scrollRange)
512         infoPtr->nPos = scrollRange;
513     else
514         infoPtr->nPos = newPos;
515
516     TRACE("[%p] pos=%d, oldpos=%d\n", infoPtr->hwndSelf, infoPtr->nPos, oldPos);
517
518     if (infoPtr->nPos != oldPos)
519     {
520         /* gray and restore btns, and if from WM_SETPOS, hide the gray btns */
521         PAGER_UpdateBtns(infoPtr, scrollRange, !fromBtnPress);
522         PAGER_PositionChildWnd(infoPtr);
523     }
524
525     return 0;
526 }
527
528 static LRESULT
529 PAGER_WindowPosChanging(PAGER_INFO* infoPtr, WINDOWPOS *winpos)
530 {
531     if ((infoPtr->dwStyle & CCS_NORESIZE) && !(winpos->flags & SWP_NOSIZE))
532     {
533         /* don't let the app resize the nonscrollable dimension of a control
534          * that was created with CCS_NORESIZE style
535          * (i.e. height for a horizontal pager, or width for a vertical one) */
536
537         /* except if the current dimension is 0 and app is setting for
538          * first time, then save amount as dimension. - GA 8/01 */
539
540         if (infoPtr->dwStyle & PGS_HORZ)
541             if (!infoPtr->nHeight && winpos->cy)
542                 infoPtr->nHeight = winpos->cy;
543             else
544                 winpos->cy = infoPtr->nHeight;
545         else
546             if (!infoPtr->nWidth && winpos->cx)
547                 infoPtr->nWidth = winpos->cx;
548             else
549                 winpos->cx = infoPtr->nWidth;
550         return 0;
551     }
552
553     return DefWindowProcW (infoPtr->hwndSelf, WM_WINDOWPOSCHANGING, 0, (LPARAM)winpos);
554 }
555
556 static INT
557 PAGER_SetFixedWidth(PAGER_INFO* infoPtr)
558 {
559   /* Must set the non-scrollable dimension to be less than the full height/width
560    * so that NCCalcSize is called.  The Msoft docs mention 3/4 factor for button
561    * size, and experimentation shows that affect is almost right. */
562
563     RECT wndRect;
564     INT delta, h;
565     GetWindowRect(infoPtr->hwndSelf, &wndRect);
566
567     /* see what the app says for btn width */
568     PAGER_CalcSize(infoPtr, &infoPtr->nWidth, TRUE);
569
570     if (infoPtr->dwStyle & CCS_NORESIZE)
571     {
572         delta = wndRect.right - wndRect.left - infoPtr->nWidth;
573         if (delta > infoPtr->nButtonSize)
574             infoPtr->nWidth += 4 * infoPtr->nButtonSize / 3;
575         else if (delta > 0)
576             infoPtr->nWidth +=  infoPtr->nButtonSize / 3;
577     }
578
579     h = wndRect.bottom - wndRect.top + infoPtr->nButtonSize;
580
581     TRACE("[%p] infoPtr->nWidth set to %d\n",
582                infoPtr->hwndSelf, infoPtr->nWidth);
583
584     return h;
585 }
586
587 static INT
588 PAGER_SetFixedHeight(PAGER_INFO* infoPtr)
589 {
590   /* Must set the non-scrollable dimension to be less than the full height/width
591    * so that NCCalcSize is called.  The Msoft docs mention 3/4 factor for button
592    * size, and experimentation shows that affect is almost right. */
593
594     RECT wndRect;
595     INT delta, w;
596     GetWindowRect(infoPtr->hwndSelf, &wndRect);
597
598     /* see what the app says for btn height */
599     PAGER_CalcSize(infoPtr, &infoPtr->nHeight, FALSE);
600
601     if (infoPtr->dwStyle & CCS_NORESIZE)
602     {
603         delta = wndRect.bottom - wndRect.top - infoPtr->nHeight;
604         if (delta > infoPtr->nButtonSize)
605             infoPtr->nHeight += infoPtr->nButtonSize;
606         else if (delta > 0)
607             infoPtr->nHeight +=  infoPtr->nButtonSize / 3;
608     }
609
610     w = wndRect.right - wndRect.left + infoPtr->nButtonSize;
611
612     TRACE("[%p] infoPtr->nHeight set to %d\n",
613                infoPtr->hwndSelf, infoPtr->nHeight);
614
615     return w;
616 }
617
618 /******************************************************************
619  * For the PGM_RECALCSIZE message (but not the other uses in      *
620  * this module), the native control does only the following:      *
621  *                                                                *
622  *    if (some condition)                                         *
623  *          PostMessageW(hwnd, EM_FMTLINES, 0, 0);                *
624  *    return DefWindowProcW(hwnd, PGM_RECALCSIZE, 0, 0);          *
625  *                                                                *
626  * When we figure out what the "some condition" is we will        *
627  * implement that for the message processing.                     *
628  ******************************************************************/
629
630 static LRESULT
631 PAGER_RecalcSize(PAGER_INFO *infoPtr)
632 {
633     TRACE("[%p]\n", infoPtr->hwndSelf);
634
635     if (infoPtr->hwndChild)
636     {
637         INT scrollRange = PAGER_GetScrollRange(infoPtr);
638
639         if (scrollRange <= 0)
640         {
641             infoPtr->nPos = -1;
642             PAGER_SetPos(infoPtr, 0, FALSE);
643         }
644         else
645             PAGER_PositionChildWnd(infoPtr);
646     }
647
648     return 1;
649 }
650
651
652 static COLORREF
653 PAGER_SetBkColor (PAGER_INFO* infoPtr, COLORREF clrBk)
654 {
655     COLORREF clrTemp = infoPtr->clrBk;
656
657     infoPtr->clrBk = clrBk;
658     TRACE("[%p] %06lx\n", infoPtr->hwndSelf, infoPtr->clrBk);
659
660     /* the native control seems to do things this way */
661     SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
662                  SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
663                  SWP_NOZORDER | SWP_NOACTIVATE);
664
665     RedrawWindow(infoPtr->hwndSelf, 0, 0, RDW_ERASE | RDW_INVALIDATE);
666
667     return clrTemp;
668 }
669
670
671 static INT
672 PAGER_SetBorder (PAGER_INFO* infoPtr, INT iBorder)
673 {
674     INT nTemp = infoPtr->nBorder;
675
676     infoPtr->nBorder = iBorder;
677     TRACE("[%p] %d\n", infoPtr->hwndSelf, infoPtr->nBorder);
678
679     PAGER_RecalcSize(infoPtr);
680
681     return nTemp;
682 }
683
684
685 static INT
686 PAGER_SetButtonSize (PAGER_INFO* infoPtr, INT iButtonSize)
687 {
688     INT nTemp = infoPtr->nButtonSize;
689
690     infoPtr->nButtonSize = iButtonSize;
691     TRACE("[%p] %d\n", infoPtr->hwndSelf, infoPtr->nButtonSize);
692
693     PAGER_RecalcSize(infoPtr);
694
695     return nTemp;
696 }
697
698
699 static LRESULT
700 PAGER_SetChild (PAGER_INFO* infoPtr, HWND hwndChild)
701 {
702     INT hw;
703
704     infoPtr->hwndChild = IsWindow (hwndChild) ? hwndChild : 0;
705
706     if (infoPtr->hwndChild)
707     {
708         TRACE("[%p] hwndChild=%p\n", infoPtr->hwndSelf, infoPtr->hwndChild);
709
710         if (infoPtr->dwStyle & PGS_HORZ) {
711             hw = PAGER_SetFixedHeight(infoPtr);
712             /* adjust non-scrollable dimension to fit the child */
713             SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, hw, infoPtr->nHeight,
714                          SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOZORDER |
715                          SWP_NOSIZE | SWP_NOACTIVATE);
716         }
717         else {
718             hw = PAGER_SetFixedWidth(infoPtr);
719             /* adjust non-scrollable dimension to fit the child */
720             SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, infoPtr->nWidth, hw,
721                          SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOZORDER |
722                          SWP_NOSIZE | SWP_NOACTIVATE);
723         }
724
725         /* position child within the page scroller */
726         SetWindowPos(infoPtr->hwndChild, HWND_TOP,
727                      0,0,0,0,
728                      SWP_SHOWWINDOW | SWP_NOSIZE);  /* native is 0 */
729
730         infoPtr->nPos = -1;
731         PAGER_SetPos(infoPtr, 0, FALSE);
732     }
733
734     return 0;
735 }
736
737 static void
738 PAGER_Scroll(PAGER_INFO* infoPtr, INT dir)
739 {
740     NMPGSCROLL nmpgScroll;
741     RECT rcWnd;
742
743     if (infoPtr->hwndChild)
744     {
745         ZeroMemory (&nmpgScroll, sizeof (NMPGSCROLL));
746         nmpgScroll.hdr.hwndFrom = infoPtr->hwndSelf;
747         nmpgScroll.hdr.idFrom   = GetWindowLongPtrW (infoPtr->hwndSelf, GWLP_ID);
748         nmpgScroll.hdr.code = PGN_SCROLL;
749
750         GetWindowRect(infoPtr->hwndSelf, &rcWnd);
751         GetClientRect(infoPtr->hwndSelf, &nmpgScroll.rcParent);
752         nmpgScroll.iXpos = nmpgScroll.iYpos = 0;
753         nmpgScroll.iDir = dir;
754
755         if (infoPtr->dwStyle & PGS_HORZ)
756         {
757             nmpgScroll.iScroll = rcWnd.right - rcWnd.left;
758             nmpgScroll.iXpos = infoPtr->nPos;
759         }
760         else
761         {
762             nmpgScroll.iScroll = rcWnd.bottom - rcWnd.top;
763             nmpgScroll.iYpos = infoPtr->nPos;
764         }
765         nmpgScroll.iScroll -= 2*infoPtr->nButtonSize;
766
767         SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
768                     (WPARAM)nmpgScroll.hdr.idFrom, (LPARAM)&nmpgScroll);
769
770         TRACE("[%p] PGN_SCROLL returns iScroll=%d\n", infoPtr->hwndSelf, nmpgScroll.iScroll);
771
772         if (nmpgScroll.iScroll > 0)
773         {
774             infoPtr->direction = dir;
775
776             if (dir == PGF_SCROLLLEFT || dir == PGF_SCROLLUP)
777                 PAGER_SetPos(infoPtr, infoPtr->nPos - nmpgScroll.iScroll, TRUE);
778             else
779                 PAGER_SetPos(infoPtr, infoPtr->nPos + nmpgScroll.iScroll, TRUE);
780         }
781         else
782             infoPtr->direction = -1;
783     }
784 }
785
786 static LRESULT
787 PAGER_FmtLines(PAGER_INFO *infoPtr)
788 {
789     /* initiate NCCalcSize to resize client wnd and get size */
790     SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
791                  SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
792                  SWP_NOZORDER | SWP_NOACTIVATE);
793
794     SetWindowPos(infoPtr->hwndChild, 0,
795                  0,0,infoPtr->nWidth,infoPtr->nHeight,
796                  0);
797
798     return DefWindowProcW (infoPtr->hwndSelf, EM_FMTLINES, 0, 0);
799 }
800
801 static LRESULT
802 PAGER_Create (HWND hwnd, LPCREATESTRUCTW lpcs)
803 {
804     PAGER_INFO *infoPtr;
805
806     /* allocate memory for info structure */
807     infoPtr = (PAGER_INFO *)Alloc (sizeof(PAGER_INFO));
808     if (!infoPtr) return -1;
809     SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
810
811     /* set default settings */
812     infoPtr->hwndSelf = hwnd;
813     infoPtr->hwndChild = NULL;
814     infoPtr->hwndNotify = lpcs->hwndParent;
815     infoPtr->dwStyle = lpcs->style;
816     infoPtr->clrBk = GetSysColor(COLOR_BTNFACE);
817     infoPtr->nBorder = 0;
818     infoPtr->nButtonSize = 12;
819     infoPtr->nPos = 0;
820     infoPtr->nWidth = 0;
821     infoPtr->nHeight = 0;
822     infoPtr->bForward = FALSE;
823     infoPtr->bCapture = FALSE;
824     infoPtr->TLbtnState = PGF_INVISIBLE;
825     infoPtr->BRbtnState = PGF_INVISIBLE;
826     infoPtr->direction = -1;
827
828     if (infoPtr->dwStyle & PGS_DRAGNDROP)
829         FIXME("[%p] Drag and Drop style is not implemented yet.\n", infoPtr->hwndSelf);
830
831     return 0;
832 }
833
834
835 static LRESULT
836 PAGER_Destroy (PAGER_INFO *infoPtr)
837 {
838     SetWindowLongPtrW (infoPtr->hwndSelf, 0, 0);
839     Free (infoPtr);  /* free pager info data */
840     return 0;
841 }
842
843 static LRESULT
844 PAGER_NCCalcSize(PAGER_INFO* infoPtr, WPARAM wParam, LPRECT lpRect)
845 {
846     RECT rcChild, rcWindow;
847     INT scrollRange;
848
849     /*
850      * lpRect points to a RECT struct.  On entry, the struct
851      * contains the proposed wnd rectangle for the window.
852      * On exit, the struct should contain the screen
853      * coordinates of the corresponding window's client area.
854      */
855
856     DefWindowProcW (infoPtr->hwndSelf, WM_NCCALCSIZE, wParam, (LPARAM)lpRect);
857
858     TRACE("orig rect=%s\n", wine_dbgstr_rect(lpRect));
859
860     GetWindowRect (infoPtr->hwndChild, &rcChild);
861     MapWindowPoints (0, infoPtr->hwndSelf, (LPPOINT)&rcChild, 2); /* FIXME: RECT != 2 POINTS */
862     GetWindowRect (infoPtr->hwndSelf, &rcWindow);
863
864     if (infoPtr->dwStyle & PGS_HORZ)
865     {
866         infoPtr->nWidth = lpRect->right - lpRect->left;
867         PAGER_CalcSize (infoPtr, &infoPtr->nWidth, TRUE);
868
869         scrollRange = infoPtr->nWidth - (rcWindow.right - rcWindow.left);
870
871         if (infoPtr->TLbtnState && (lpRect->left + infoPtr->nButtonSize < lpRect->right))
872             lpRect->left += infoPtr->nButtonSize;
873         if (infoPtr->BRbtnState && (lpRect->right - infoPtr->nButtonSize > lpRect->left))
874             lpRect->right -= infoPtr->nButtonSize;
875     }
876     else
877     {
878         infoPtr->nHeight = lpRect->bottom - lpRect->top;
879         PAGER_CalcSize (infoPtr, &infoPtr->nHeight, FALSE);
880
881         scrollRange = infoPtr->nHeight - (rcWindow.bottom - rcWindow.top);
882
883         if (infoPtr->TLbtnState && (lpRect->top + infoPtr->nButtonSize < lpRect->bottom))
884             lpRect->top += infoPtr->nButtonSize;
885         if (infoPtr->BRbtnState && (lpRect->bottom - infoPtr->nButtonSize > lpRect->top))
886             lpRect->bottom -= infoPtr->nButtonSize;
887     }
888
889     TRACE("nPos=%d, nHeigth=%d, window=%s\n",
890           infoPtr->nPos, infoPtr->nHeight,
891           wine_dbgstr_rect(&rcWindow));
892
893     TRACE("[%p] client rect set to %ldx%ld at (%ld,%ld) BtnState[%d,%d]\n",
894           infoPtr->hwndSelf, lpRect->right-lpRect->left, lpRect->bottom-lpRect->top,
895           lpRect->left, lpRect->top,
896           infoPtr->TLbtnState, infoPtr->BRbtnState);
897
898     return 0;
899 }
900
901 static LRESULT
902 PAGER_NCPaint (PAGER_INFO* infoPtr, HRGN hRgn)
903 {
904     RECT rcBottomRight, rcTopLeft;
905     HDC hdc;
906
907     if (infoPtr->dwStyle & WS_MINIMIZE)
908         return 0;
909
910     DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)hRgn, 0);
911
912     if (!(hdc = GetDCEx (infoPtr->hwndSelf, 0, DCX_USESTYLE | DCX_WINDOW)))
913         return 0;
914
915     PAGER_GetButtonRects(infoPtr, &rcTopLeft, &rcBottomRight, FALSE);
916
917     PAGER_DrawButton(hdc, infoPtr->clrBk, rcTopLeft,
918                      infoPtr->dwStyle & PGS_HORZ, TRUE, infoPtr->TLbtnState);
919     PAGER_DrawButton(hdc, infoPtr->clrBk, rcBottomRight,
920                      infoPtr->dwStyle & PGS_HORZ, FALSE, infoPtr->BRbtnState);
921
922     ReleaseDC( infoPtr->hwndSelf, hdc );
923     return 0;
924 }
925
926 static INT
927 PAGER_HitTest (PAGER_INFO* infoPtr, const POINT * pt)
928 {
929     RECT clientRect, rcTopLeft, rcBottomRight;
930     POINT ptWindow;
931
932     GetClientRect (infoPtr->hwndSelf, &clientRect);
933
934     if (PtInRect(&clientRect, *pt))
935     {
936         TRACE("child\n");
937         return -1;
938     }
939
940     ptWindow = *pt;
941     PAGER_GetButtonRects(infoPtr, &rcTopLeft, &rcBottomRight, TRUE);
942
943     if ((infoPtr->TLbtnState != PGF_INVISIBLE) && PtInRect(&rcTopLeft, ptWindow))
944     {
945         TRACE("PGB_TOPORLEFT\n");
946         return PGB_TOPORLEFT;
947     }
948     else if ((infoPtr->BRbtnState != PGF_INVISIBLE) && PtInRect(&rcBottomRight, ptWindow))
949     {
950         TRACE("PGB_BOTTOMORRIGHT\n");
951         return PGB_BOTTOMORRIGHT;
952     }
953
954     TRACE("nowhere\n");
955     return -1;
956 }
957
958 static LRESULT
959 PAGER_NCHitTest (PAGER_INFO* infoPtr, INT x, INT y)
960 {
961     POINT pt;
962     INT nHit;
963
964     pt.x = x;
965     pt.y = y;
966
967     ScreenToClient (infoPtr->hwndSelf, &pt);
968     nHit = PAGER_HitTest(infoPtr, &pt);
969
970     return (nHit < 0) ? HTTRANSPARENT : HTCLIENT;
971 }
972
973 static LRESULT
974 PAGER_MouseMove (PAGER_INFO* infoPtr, INT keys, INT x, INT y)
975 {
976     POINT clpt, pt;
977     RECT wnrect, *btnrect = NULL;
978     BOOL topLeft = FALSE;
979     INT btnstate = 0;
980     INT hit;
981     HDC hdc;
982
983     pt.x = x;
984     pt.y = y;
985
986     TRACE("[%p] to (%d,%d)\n", infoPtr->hwndSelf, x, y);
987     ClientToScreen(infoPtr->hwndSelf, &pt);
988     GetWindowRect(infoPtr->hwndSelf, &wnrect);
989     if (PtInRect(&wnrect, pt)) {
990         RECT TLbtnrect, BRbtnrect;
991         PAGER_GetButtonRects(infoPtr, &TLbtnrect, &BRbtnrect, FALSE);
992
993         clpt = pt;
994         MapWindowPoints(0, infoPtr->hwndSelf, &clpt, 1);
995         hit = PAGER_HitTest(infoPtr, &clpt);
996         if ((hit == PGB_TOPORLEFT) && (infoPtr->TLbtnState == PGF_NORMAL))
997         {
998             topLeft = TRUE;
999             btnrect = &TLbtnrect;
1000             infoPtr->TLbtnState = PGF_HOT;
1001             btnstate = infoPtr->TLbtnState;
1002         }
1003         else if ((hit == PGB_BOTTOMORRIGHT) && (infoPtr->BRbtnState == PGF_NORMAL))
1004         {
1005             topLeft = FALSE;
1006             btnrect = &BRbtnrect;
1007             infoPtr->BRbtnState = PGF_HOT;
1008             btnstate = infoPtr->BRbtnState;
1009         }
1010
1011         /* If in one of the buttons the capture and draw buttons */
1012         if (btnrect)
1013         {
1014             TRACE("[%p] draw btn (%ld,%ld)-(%ld,%ld), Capture %s, style %08lx\n",
1015                   infoPtr->hwndSelf, btnrect->left, btnrect->top,
1016                   btnrect->right, btnrect->bottom,
1017                   (infoPtr->bCapture) ? "TRUE" : "FALSE",
1018                   infoPtr->dwStyle);
1019             if (!infoPtr->bCapture)
1020             {
1021                 TRACE("[%p] SetCapture\n", infoPtr->hwndSelf);
1022                 SetCapture(infoPtr->hwndSelf);
1023                 infoPtr->bCapture = TRUE;
1024             }
1025             if (infoPtr->dwStyle & PGS_AUTOSCROLL)
1026                 SetTimer(infoPtr->hwndSelf, TIMERID1, 0x3e, 0);
1027             hdc = GetWindowDC(infoPtr->hwndSelf);
1028             /* OffsetRect(wnrect, 0 | 1, 0 | 1) */
1029             PAGER_DrawButton(hdc, infoPtr->clrBk, *btnrect,
1030                              infoPtr->dwStyle & PGS_HORZ, topLeft, btnstate);
1031             ReleaseDC(infoPtr->hwndSelf, hdc);
1032             return 0;
1033         }
1034     }
1035
1036     /* If we think we are captured, then do release */
1037     if (infoPtr->bCapture && (WindowFromPoint(pt) != infoPtr->hwndSelf))
1038     {
1039         NMHDR nmhdr;
1040
1041         infoPtr->bCapture = FALSE;
1042
1043         if (GetCapture() == infoPtr->hwndSelf)
1044         {
1045             ReleaseCapture();
1046
1047             if (infoPtr->TLbtnState == PGF_GRAYED)
1048             {
1049                 infoPtr->TLbtnState = PGF_INVISIBLE;
1050                 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
1051                              SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
1052                              SWP_NOZORDER | SWP_NOACTIVATE);
1053             }
1054             else if (infoPtr->TLbtnState == PGF_HOT)
1055             {
1056                 infoPtr->TLbtnState = PGF_NORMAL;
1057                 /* FIXME: just invalidate button rect */
1058                 RedrawWindow(infoPtr->hwndSelf, NULL, NULL, RDW_FRAME | RDW_INVALIDATE);
1059             }
1060
1061             if (infoPtr->BRbtnState == PGF_GRAYED)
1062             {
1063                 infoPtr->BRbtnState = PGF_INVISIBLE;
1064                 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
1065                              SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
1066                              SWP_NOZORDER | SWP_NOACTIVATE);
1067             }
1068             else if (infoPtr->BRbtnState == PGF_HOT)
1069             {
1070                 infoPtr->BRbtnState = PGF_NORMAL;
1071                 /* FIXME: just invalidate button rect */
1072                 RedrawWindow(infoPtr->hwndSelf, NULL, NULL, RDW_FRAME | RDW_INVALIDATE);
1073             }
1074
1075             /* Notify parent of released mouse capture */
1076                 memset(&nmhdr, 0, sizeof(NMHDR));
1077                 nmhdr.hwndFrom = infoPtr->hwndSelf;
1078                 nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1079                 nmhdr.code = NM_RELEASEDCAPTURE;
1080                 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
1081                          (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
1082         }
1083         if (IsWindow(infoPtr->hwndSelf))
1084             KillTimer(infoPtr->hwndSelf, TIMERID1);
1085     }
1086     return 0;
1087 }
1088
1089 static LRESULT
1090 PAGER_LButtonDown (PAGER_INFO* infoPtr, INT keys, INT x, INT y)
1091 {
1092     BOOL repaintBtns = FALSE;
1093     POINT pt;
1094     INT hit;
1095
1096     pt.x = x;
1097     pt.y = y;
1098
1099     TRACE("[%p] at (%d,%d)\n", infoPtr->hwndSelf, x, y);
1100
1101     hit = PAGER_HitTest(infoPtr, &pt);
1102
1103     /* put btn in DEPRESSED state */
1104     if (hit == PGB_TOPORLEFT)
1105     {
1106         repaintBtns = infoPtr->TLbtnState != PGF_DEPRESSED;
1107         infoPtr->TLbtnState = PGF_DEPRESSED;
1108         SetTimer(infoPtr->hwndSelf, TIMERID1, INITIAL_DELAY, 0);
1109     }
1110     else if (hit == PGB_BOTTOMORRIGHT)
1111     {
1112         repaintBtns = infoPtr->BRbtnState != PGF_DEPRESSED;
1113         infoPtr->BRbtnState = PGF_DEPRESSED;
1114         SetTimer(infoPtr->hwndSelf, TIMERID1, INITIAL_DELAY, 0);
1115     }
1116
1117     if (repaintBtns)
1118         SendMessageW(infoPtr->hwndSelf, WM_NCPAINT, 0, 0);
1119
1120     switch(hit)
1121     {
1122     case PGB_TOPORLEFT:
1123         if (infoPtr->dwStyle & PGS_HORZ)
1124         {
1125             TRACE("[%p] PGF_SCROLLLEFT\n", infoPtr->hwndSelf);
1126             PAGER_Scroll(infoPtr, PGF_SCROLLLEFT);
1127         }
1128         else
1129         {
1130             TRACE("[%p] PGF_SCROLLUP\n", infoPtr->hwndSelf);
1131             PAGER_Scroll(infoPtr, PGF_SCROLLUP);
1132         }
1133         break;
1134     case PGB_BOTTOMORRIGHT:
1135         if (infoPtr->dwStyle & PGS_HORZ)
1136         {
1137             TRACE("[%p] PGF_SCROLLRIGHT\n", infoPtr->hwndSelf);
1138             PAGER_Scroll(infoPtr, PGF_SCROLLRIGHT);
1139         }
1140         else
1141         {
1142             TRACE("[%p] PGF_SCROLLDOWN\n", infoPtr->hwndSelf);
1143             PAGER_Scroll(infoPtr, PGF_SCROLLDOWN);
1144         }
1145         break;
1146     default:
1147         break;
1148     }
1149
1150     return 0;
1151 }
1152
1153 static LRESULT
1154 PAGER_LButtonUp (PAGER_INFO* infoPtr, INT keys, INT x, INT y)
1155 {
1156     TRACE("[%p]\n", infoPtr->hwndSelf);
1157
1158     KillTimer (infoPtr->hwndSelf, TIMERID1);
1159     KillTimer (infoPtr->hwndSelf, TIMERID2);
1160
1161     /* make PRESSED btns NORMAL but don't hide gray btns */
1162     if (infoPtr->TLbtnState & (PGF_HOT | PGF_DEPRESSED))
1163         infoPtr->TLbtnState = PGF_NORMAL;
1164     if (infoPtr->BRbtnState & (PGF_HOT | PGF_DEPRESSED))
1165         infoPtr->BRbtnState = PGF_NORMAL;
1166
1167     return 0;
1168 }
1169
1170 static LRESULT
1171 PAGER_Timer (PAGER_INFO* infoPtr, INT nTimerId)
1172 {
1173     INT dir;
1174
1175     /* if initial timer, kill it and start the repeat timer */
1176     if (nTimerId == TIMERID1) {
1177         if (infoPtr->TLbtnState == PGF_HOT)
1178             dir = (infoPtr->dwStyle & PGS_HORZ) ?
1179                 PGF_SCROLLLEFT : PGF_SCROLLUP;
1180         else
1181             dir = (infoPtr->dwStyle & PGS_HORZ) ?
1182                 PGF_SCROLLRIGHT : PGF_SCROLLDOWN;
1183         TRACE("[%p] TIMERID1: style=%08lx, dir=%d\n", 
1184               infoPtr->hwndSelf, infoPtr->dwStyle, dir);
1185         KillTimer(infoPtr->hwndSelf, TIMERID1);
1186         SetTimer(infoPtr->hwndSelf, TIMERID1, REPEAT_DELAY, 0);
1187         if (infoPtr->dwStyle & PGS_AUTOSCROLL) {
1188             PAGER_Scroll(infoPtr, dir);
1189             SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0,
1190                          SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
1191                          SWP_NOZORDER | SWP_NOACTIVATE);
1192         }
1193         return 0;
1194
1195     }
1196
1197     TRACE("[%p] TIMERID2: dir=%d\n", infoPtr->hwndSelf, infoPtr->direction);
1198     KillTimer(infoPtr->hwndSelf, TIMERID2);
1199     if (infoPtr->direction > 0) {
1200         PAGER_Scroll(infoPtr, infoPtr->direction);
1201         SetTimer(infoPtr->hwndSelf, TIMERID2, REPEAT_DELAY, 0);
1202     }
1203     return 0;
1204 }
1205
1206 static LRESULT
1207 PAGER_EraseBackground (PAGER_INFO* infoPtr, HDC hdc)
1208 {
1209     POINT pt, ptorig;
1210     HWND parent;
1211
1212     pt.x = 0;
1213     pt.y = 0;
1214     parent = GetParent(infoPtr->hwndSelf);
1215     MapWindowPoints(infoPtr->hwndSelf, parent, &pt, 1);
1216     OffsetWindowOrgEx (hdc, pt.x, pt.y, &ptorig);
1217     SendMessageW (parent, WM_ERASEBKGND, (WPARAM)hdc, 0);
1218     SetWindowOrgEx (hdc, ptorig.x, ptorig.y, 0);
1219
1220     return 0;
1221 }
1222
1223
1224 static LRESULT
1225 PAGER_Size (PAGER_INFO* infoPtr, INT type, INT x, INT y)
1226 {
1227     /* note that WM_SIZE is sent whenever NCCalcSize resizes the client wnd */
1228
1229     TRACE("[%p] %d,%d\n", infoPtr->hwndSelf, x, y);
1230
1231     if (infoPtr->dwStyle & PGS_HORZ)
1232         infoPtr->nHeight = y;
1233     else
1234         infoPtr->nWidth = x;
1235
1236     return PAGER_RecalcSize(infoPtr);
1237 }
1238
1239
1240 static LRESULT 
1241 PAGER_StyleChanged(PAGER_INFO *infoPtr, WPARAM wStyleType, LPSTYLESTRUCT lpss)
1242 {
1243     DWORD oldStyle = infoPtr->dwStyle;
1244
1245     TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
1246           wStyleType, lpss->styleOld, lpss->styleNew);
1247
1248     if (wStyleType != GWL_STYLE) return 0;
1249   
1250     infoPtr->dwStyle = lpss->styleNew;
1251
1252     if ((oldStyle ^ lpss->styleNew) & (PGS_HORZ | PGS_VERT))
1253     {
1254         PAGER_RecalcSize(infoPtr);
1255     }
1256
1257     return 0;
1258 }
1259
1260 static LRESULT WINAPI
1261 PAGER_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1262 {
1263     PAGER_INFO *infoPtr = (PAGER_INFO *)GetWindowLongPtrW(hwnd, 0);
1264
1265     if (!infoPtr && (uMsg != WM_CREATE))
1266         return DefWindowProcW (hwnd, uMsg, wParam, lParam);
1267
1268     switch (uMsg)
1269     {
1270         case EM_FMTLINES:
1271             return PAGER_FmtLines(infoPtr);
1272
1273         case PGM_FORWARDMOUSE:
1274             return PAGER_ForwardMouse (infoPtr, (BOOL)wParam);
1275
1276         case PGM_GETBKCOLOR:
1277             return PAGER_GetBkColor(infoPtr);
1278
1279         case PGM_GETBORDER:
1280             return PAGER_GetBorder(infoPtr);
1281
1282         case PGM_GETBUTTONSIZE:
1283             return PAGER_GetButtonSize(infoPtr);
1284
1285         case PGM_GETPOS:
1286             return PAGER_GetPos(infoPtr);
1287
1288         case PGM_GETBUTTONSTATE:
1289             return PAGER_GetButtonState (infoPtr, (INT)lParam);
1290
1291 /*      case PGM_GETDROPTARGET: */
1292
1293         case PGM_RECALCSIZE:
1294             return PAGER_RecalcSize(infoPtr);
1295
1296         case PGM_SETBKCOLOR:
1297             return PAGER_SetBkColor (infoPtr, (COLORREF)lParam);
1298
1299         case PGM_SETBORDER:
1300             return PAGER_SetBorder (infoPtr, (INT)lParam);
1301
1302         case PGM_SETBUTTONSIZE:
1303             return PAGER_SetButtonSize (infoPtr, (INT)lParam);
1304
1305         case PGM_SETCHILD:
1306             return PAGER_SetChild (infoPtr, (HWND)lParam);
1307
1308         case PGM_SETPOS:
1309             return PAGER_SetPos(infoPtr, (INT)lParam, FALSE);
1310
1311         case WM_CREATE:
1312             return PAGER_Create (hwnd, (LPCREATESTRUCTW)lParam);
1313
1314         case WM_DESTROY:
1315             return PAGER_Destroy (infoPtr);
1316
1317         case WM_SIZE:
1318             return PAGER_Size (infoPtr, (INT)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
1319
1320         case WM_NCPAINT:
1321             return PAGER_NCPaint (infoPtr, (HRGN)wParam);
1322
1323         case WM_WINDOWPOSCHANGING:
1324             return PAGER_WindowPosChanging (infoPtr, (WINDOWPOS*)lParam);
1325
1326         case WM_STYLECHANGED:
1327             return PAGER_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
1328
1329         case WM_NCCALCSIZE:
1330             return PAGER_NCCalcSize (infoPtr, wParam, (LPRECT)lParam);
1331
1332         case WM_NCHITTEST:
1333             return PAGER_NCHitTest (infoPtr, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
1334
1335         case WM_MOUSEMOVE:
1336             if (infoPtr->bForward && infoPtr->hwndChild)
1337                 PostMessageW(infoPtr->hwndChild, WM_MOUSEMOVE, wParam, lParam);
1338             return PAGER_MouseMove (infoPtr, (INT)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
1339
1340         case WM_LBUTTONDOWN:
1341             return PAGER_LButtonDown (infoPtr, (INT)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
1342
1343         case WM_LBUTTONUP:
1344             return PAGER_LButtonUp (infoPtr, (INT)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
1345
1346         case WM_ERASEBKGND:
1347             return PAGER_EraseBackground (infoPtr, (HDC)wParam);
1348
1349         case WM_TIMER:
1350             return PAGER_Timer (infoPtr, (INT)wParam);
1351
1352         case WM_NOTIFY:
1353         case WM_COMMAND:
1354             return SendMessageW (infoPtr->hwndNotify, uMsg, wParam, lParam);
1355
1356         default:
1357             return DefWindowProcW (hwnd, uMsg, wParam, lParam);
1358     }
1359 }
1360
1361
1362 VOID
1363 PAGER_Register (void)
1364 {
1365     WNDCLASSW wndClass;
1366
1367     ZeroMemory (&wndClass, sizeof(WNDCLASSW));
1368     wndClass.style         = CS_GLOBALCLASS;
1369     wndClass.lpfnWndProc   = PAGER_WindowProc;
1370     wndClass.cbClsExtra    = 0;
1371     wndClass.cbWndExtra    = sizeof(PAGER_INFO *);
1372     wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
1373     wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
1374     wndClass.lpszClassName = WC_PAGESCROLLERW;
1375
1376     RegisterClassW (&wndClass);
1377 }
1378
1379
1380 VOID
1381 PAGER_Unregister (void)
1382 {
1383     UnregisterClassW (WC_PAGESCROLLERW, NULL);
1384 }