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