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