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