Updated.
[wine] / dlls / comctl32 / pager.c
1 /*
2  * Pager control
3  *
4  * Copyright 1998, 1999 Eric Kohl
5  *
6  * NOTES
7  *    Tested primarily with the controlspy Pager application.
8  *       Susan Farley (susan@codeweavers.com)
9  *
10  * TODO:
11  *    Implement repetitive button press.
12  *    Adjust arrow size relative to size of button.
13  *    Allow border size changes.
14  *    Implement drag and drop style.
15  */
16
17 #include <string.h>
18 #include "winbase.h"
19 #include "commctrl.h"
20 #include "debugtools.h"
21
22 DEFAULT_DEBUG_CHANNEL(pager);
23
24 typedef struct
25 {
26     HWND   hwndChild;  /* handle of the contained wnd */
27     BOOL   bNoResize;  /* set when created with CCS_NORESIZE */
28     COLORREF clrBk;    /* background color */
29     INT    nBorder;    /* border size for the control */
30     INT    nButtonSize;/* size of the pager btns */
31     INT    nPos;       /* scroll position */
32     INT    nWidth;     /* from child wnd's response to PGN_CALCSIZE */
33     INT    nHeight;    /* from child wnd's response to PGN_CALCSIZE */ 
34     BOOL   bForward;   /* forward WM_MOUSEMOVE msgs to the contained wnd */
35     INT    TLbtnState; /* state of top or left btn */
36     INT    BRbtnState; /* state of bottom or right btn */
37
38 } PAGER_INFO;
39
40 #define PAGER_GetInfoPtr(hwnd) ((PAGER_INFO *)GetWindowLongA(hwnd, 0))
41 #define PAGER_IsHorizontal(hwnd) ((GetWindowLongA (hwnd, GWL_STYLE) & PGS_HORZ))
42
43 #define MIN_ARROW_WIDTH  8
44 #define MIN_ARROW_HEIGHT 5
45
46
47 /* the horizontal arrows are: 
48  *
49  * 01234    01234
50  * 1  *      *
51  * 2 **      **
52  * 3***      ***
53  * 4***      ***
54  * 5 **      **
55  * 6  *      *
56  * 7  
57  *
58  */
59 static void
60 PAGER_DrawHorzArrow (HDC hdc, RECT r, INT colorRef, BOOL left)
61 {
62     INT x, y, w, h;
63     HPEN hOldPen;
64     
65     w = r.right - r.left + 1;
66     h = r.bottom - r.top + 1;
67     if ((h < MIN_ARROW_WIDTH) || (w < MIN_ARROW_HEIGHT))
68         return;  /* refuse to draw partial arrow */
69
70     hOldPen = SelectObject ( hdc, GetSysColorPen (colorRef));
71     if (left)
72     {
73         x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 3;
74         y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
75         MoveToEx (hdc, x, y, NULL);
76         LineTo (hdc, x--, y+5); y++;
77         MoveToEx (hdc, x, y, NULL);
78         LineTo (hdc, x--, y+3); y++;
79         MoveToEx (hdc, x, y, NULL);
80         LineTo (hdc, x, y+1);
81     }
82     else
83     {
84         x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
85         y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
86         MoveToEx (hdc, x, y, NULL);
87         LineTo (hdc, x++, y+5); y++;
88         MoveToEx (hdc, x, y, NULL);
89         LineTo (hdc, x++, y+3); y++;
90         MoveToEx (hdc, x, y, NULL);
91         LineTo (hdc, x, y+1);
92     }
93
94     SelectObject( hdc, hOldPen );
95 }
96
97 /* the vertical arrows are: 
98  *
99  * 01234567    01234567
100  * 1******        **  
101  * 2 ****        ****
102  * 3  **        ******
103  * 4
104  *
105  */
106 static void
107 PAGER_DrawVertArrow (HDC hdc, RECT r, INT colorRef, BOOL up)
108 {
109     INT x, y, w, h;
110     HPEN hOldPen;
111     
112     w = r.right - r.left + 1;
113     h = r.bottom - r.top + 1;
114     if ((h < MIN_ARROW_WIDTH) || (w < MIN_ARROW_HEIGHT))
115         return;  /* refuse to draw partial arrow */
116
117     hOldPen = SelectObject ( hdc, GetSysColorPen (colorRef));
118     if (up)
119     {
120         x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
121         y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 3;
122         MoveToEx (hdc, x, y, NULL);
123         LineTo (hdc, x+5, y--); x++;
124         MoveToEx (hdc, x, y, NULL);
125         LineTo (hdc, x+3, y--); x++;
126         MoveToEx (hdc, x, y, NULL);
127         LineTo (hdc, x+1, y);
128     }
129     else
130     {
131         x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
132         y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
133         MoveToEx (hdc, x, y, NULL);
134         LineTo (hdc, x+5, y++); x++;
135         MoveToEx (hdc, x, y, NULL);
136         LineTo (hdc, x+3, y++); x++;
137         MoveToEx (hdc, x, y, NULL);
138         LineTo (hdc, x+1, y);
139     }
140
141     SelectObject( hdc, hOldPen );
142 }
143
144 static void
145 PAGER_DrawButton(HDC hdc, COLORREF clrBk, RECT arrowRect,
146                  BOOL horz, BOOL topLeft, INT btnState)
147 {
148     HBRUSH   hBrush, hOldBrush;
149     RECT     rc = arrowRect;
150
151     if (!btnState) /* PGF_INVISIBLE */
152         return;
153
154     if ((rc.right - rc.left <= 0) || (rc.bottom - rc.top <= 0))
155         return;  
156
157     hBrush = CreateSolidBrush(clrBk);
158     hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
159
160     FillRect(hdc, &rc, hBrush);
161
162     if (btnState == PGF_HOT) 
163     {
164        rc.left++, rc.top++; rc.right++, rc.bottom++;
165        DrawEdge( hdc, &rc, EDGE_RAISED, BF_RECT);
166        if (horz)
167            PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
168        else
169            PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
170        rc.left--, rc.top--; rc.right--, rc.bottom--;
171     }
172     else if (btnState == PGF_NORMAL) 
173     {
174        DrawEdge (hdc, &rc, BDR_OUTER, BF_FLAT);
175        if (horz)
176            PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
177        else
178            PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
179     }
180     else if (btnState == PGF_DEPRESSED) 
181     {
182        rc.left++, rc.top++;
183        DrawEdge( hdc, &rc, BDR_SUNKENOUTER, BF_RECT);
184        if (horz)
185            PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
186        else
187            PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
188        rc.left--, rc.top--;
189     }
190     else if (btnState == PGF_GRAYED) 
191     {
192        DrawEdge (hdc, &rc, BDR_OUTER, BF_FLAT);
193        if (horz)
194        {
195            PAGER_DrawHorzArrow(hdc, rc, COLOR_3DHIGHLIGHT, topLeft);
196            rc.left++, rc.top++; rc.right++, rc.bottom++;
197            PAGER_DrawHorzArrow(hdc, rc, COLOR_3DSHADOW, topLeft);
198        }
199        else
200        {
201            PAGER_DrawVertArrow(hdc, rc, COLOR_3DHIGHLIGHT, topLeft);
202            rc.left++, rc.top++; rc.right++, rc.bottom++;
203            PAGER_DrawVertArrow(hdc, rc, COLOR_3DSHADOW, topLeft);
204        }
205        rc.left--, rc.top--; rc.right--, rc.bottom--;
206     }
207
208     SelectObject( hdc, hOldBrush );
209     DeleteObject(hBrush);
210 }
211
212 /* << PAGER_GetDropTarget >> */
213
214 static inline LRESULT
215 PAGER_ForwardMouse (HWND hwnd, WPARAM wParam)
216 {
217     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
218     TRACE("[%04x]\n", hwnd);
219
220     infoPtr->bForward = (BOOL)wParam;
221
222     return 0;
223 }
224
225 static inline LRESULT
226 PAGER_GetButtonState (HWND hwnd, WPARAM wParam, LPARAM lParam)
227 {
228     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd); 
229     LRESULT btnState = PGF_INVISIBLE;
230     INT btn = (INT)lParam;
231     TRACE("[%04x]\n", hwnd);
232
233     if (btn == PGB_TOPORLEFT)
234         btnState = infoPtr->TLbtnState;
235     else if (btn == PGB_BOTTOMORRIGHT)
236         btnState = infoPtr->BRbtnState;
237
238     return btnState;
239 }
240
241
242 static inline LRESULT
243 PAGER_GetPos(HWND hwnd)
244 {
245     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd); 
246     TRACE("[%04x] returns %d\n", hwnd, infoPtr->nPos);
247     return (LRESULT)infoPtr->nPos;
248 }
249
250 static inline LRESULT
251 PAGER_GetButtonSize(HWND hwnd)
252 {
253     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd); 
254     TRACE("[%04x] returns %d\n", hwnd, infoPtr->nButtonSize);
255     return (LRESULT)infoPtr->nButtonSize;
256 }
257
258 static inline LRESULT
259 PAGER_GetBorder(HWND hwnd)
260 {
261     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd); 
262     TRACE("[%04x] returns %d\n", hwnd, infoPtr->nBorder);
263     return (LRESULT)infoPtr->nBorder;
264 }
265
266 static inline LRESULT
267 PAGER_GetBkColor(HWND hwnd)
268 {
269     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd); 
270     TRACE("[%04x] returns %06lx\n", hwnd, infoPtr->clrBk);
271     return (LRESULT)infoPtr->clrBk;
272 }
273
274 static void 
275 PAGER_CalcSize (HWND hwnd, INT* size, BOOL getWidth) 
276 {
277     NMPGCALCSIZE nmpgcs;
278     ZeroMemory (&nmpgcs, sizeof (NMPGCALCSIZE));
279     nmpgcs.hdr.hwndFrom = hwnd;
280     nmpgcs.hdr.idFrom   = GetWindowLongA (hwnd, GWL_ID);
281     nmpgcs.hdr.code = PGN_CALCSIZE;
282     nmpgcs.dwFlag = getWidth ? PGF_CALCWIDTH : PGF_CALCHEIGHT;
283     nmpgcs.iWidth = getWidth ? *size : 0;
284     nmpgcs.iHeight = getWidth ? 0 : *size;
285     SendMessageA (hwnd, WM_NOTIFY,
286                   (WPARAM)nmpgcs.hdr.idFrom, (LPARAM)&nmpgcs);
287
288     *size = getWidth ? nmpgcs.iWidth : nmpgcs.iHeight;
289
290     TRACE("[%04x] PGN_CALCSIZE returns %s=%d\n", hwnd,
291                   getWidth ? "width" : "height", *size);
292 }
293
294 static void
295 PAGER_PositionChildWnd(HWND hwnd, PAGER_INFO* infoPtr)
296 {
297     if (infoPtr->hwndChild)
298     {
299         RECT rcClient;
300         int nPos = infoPtr->nPos;
301
302         /* compensate for a grayed btn, which will soon become invisible */
303         if (infoPtr->TLbtnState == PGF_GRAYED)
304             nPos += infoPtr->nButtonSize;
305
306         GetClientRect(hwnd, &rcClient);
307
308         if (PAGER_IsHorizontal(hwnd))
309         {
310             int wndSize = max(0, rcClient.right - rcClient.left);
311             if (infoPtr->nWidth < wndSize)
312                 infoPtr->nWidth = wndSize;
313
314             TRACE("[%04x] SWP %dx%d at (%d,%d)\n", hwnd,
315                          infoPtr->nWidth, infoPtr->nHeight,
316                          -nPos, 0);
317             SetWindowPos(infoPtr->hwndChild, 0,
318                          -nPos, 0,
319                          infoPtr->nWidth, infoPtr->nHeight,
320                          SWP_NOZORDER);
321         }
322         else
323         {
324             int wndSize = max(0, rcClient.bottom - rcClient.top);
325             if (infoPtr->nHeight < wndSize)
326                 infoPtr->nHeight = wndSize;
327
328             TRACE("[%04x] SWP %dx%d at (%d,%d)\n", hwnd, 
329                          infoPtr->nWidth, infoPtr->nHeight,
330                          0, -nPos);
331             SetWindowPos(infoPtr->hwndChild, 0,
332                          0, -nPos,
333                          infoPtr->nWidth, infoPtr->nHeight,
334                          SWP_NOZORDER);
335         }
336
337         InvalidateRect(infoPtr->hwndChild, NULL, TRUE);
338     }
339 }
340
341 static INT
342 PAGER_GetScrollRange(HWND hwnd, PAGER_INFO* infoPtr)
343 {
344     INT scrollRange = 0;
345
346     if (infoPtr->hwndChild)
347     {
348         INT wndSize, childSize;
349         RECT wndRect;
350         GetWindowRect(hwnd, &wndRect);
351
352         if (PAGER_IsHorizontal(hwnd))
353         {
354             wndSize = wndRect.right - wndRect.left;
355             PAGER_CalcSize(hwnd, &infoPtr->nWidth, TRUE);
356             childSize = infoPtr->nWidth;
357         }
358         else
359         {
360             wndSize = wndRect.bottom - wndRect.top;
361             PAGER_CalcSize(hwnd, &infoPtr->nHeight, FALSE);
362             childSize = infoPtr->nHeight;
363         }
364
365         TRACE("childSize = %d,  wndSize = %d\n", childSize, wndSize);
366         if (childSize > wndSize)
367             scrollRange = childSize - wndSize + infoPtr->nButtonSize;
368     }
369
370     TRACE("[%04x] returns %d\n", hwnd, scrollRange);
371     return scrollRange;
372 }
373
374 static void 
375 PAGER_GrayAndRestoreBtns(PAGER_INFO* infoPtr, INT scrollRange,
376                          BOOL* needsResize, BOOL* needsRepaint)
377 {
378     if (infoPtr->nPos > 0)
379     {
380         *needsResize |= !infoPtr->TLbtnState; /* PGF_INVISIBLE */
381         if (infoPtr->TLbtnState != PGF_DEPRESSED)
382             infoPtr->TLbtnState = PGF_NORMAL;
383     }
384     else
385     {
386         *needsRepaint |= (infoPtr->TLbtnState != PGF_GRAYED);
387         infoPtr->TLbtnState = PGF_GRAYED;
388     }
389
390     if (scrollRange <= 0)
391     {
392         *needsRepaint |= (infoPtr->TLbtnState != PGF_GRAYED);
393         infoPtr->TLbtnState = PGF_GRAYED;
394         *needsRepaint |= (infoPtr->BRbtnState != PGF_GRAYED);
395         infoPtr->BRbtnState = PGF_GRAYED;
396     }
397     else if (infoPtr->nPos < scrollRange)
398     {
399         *needsResize |= !infoPtr->BRbtnState; /* PGF_INVISIBLE */
400         if (infoPtr->BRbtnState != PGF_DEPRESSED)
401             infoPtr->BRbtnState = PGF_NORMAL;
402     }
403     else
404     {
405         *needsRepaint |= (infoPtr->BRbtnState != PGF_GRAYED);
406         infoPtr->BRbtnState = PGF_GRAYED;
407     }
408 }
409
410
411 static void 
412 PAGER_NormalizeBtns(PAGER_INFO* infoPtr, BOOL* needsRepaint)
413 {
414     if (infoPtr->TLbtnState & (PGF_HOT | PGF_DEPRESSED))
415     {
416         infoPtr->TLbtnState = PGF_NORMAL;
417         *needsRepaint = TRUE;
418     }
419
420     if (infoPtr->BRbtnState & (PGF_HOT | PGF_DEPRESSED))
421     {
422         infoPtr->BRbtnState = PGF_NORMAL;
423         *needsRepaint = TRUE;
424     }
425 }
426
427 static void 
428 PAGER_HideGrayBtns(PAGER_INFO* infoPtr, BOOL* needsResize)
429 {
430     if (infoPtr->TLbtnState == PGF_GRAYED)
431     {
432         infoPtr->TLbtnState = PGF_INVISIBLE;
433         *needsResize = TRUE;
434     }
435
436     if (infoPtr->BRbtnState == PGF_GRAYED)
437     {
438         infoPtr->BRbtnState = PGF_INVISIBLE;
439         *needsResize = TRUE;
440     }
441 }
442
443 static void
444 PAGER_UpdateBtns(HWND hwnd, PAGER_INFO *infoPtr,
445                  INT scrollRange, BOOL hideGrayBtns)
446 {
447     BOOL resizeClient = FALSE;
448     BOOL repaintBtns = FALSE;
449
450     if (scrollRange < 0)
451         PAGER_NormalizeBtns(infoPtr, &repaintBtns);
452     else
453         PAGER_GrayAndRestoreBtns(infoPtr, scrollRange, &resizeClient, &repaintBtns);
454
455     if (hideGrayBtns)
456         PAGER_HideGrayBtns(infoPtr, &resizeClient);
457
458     if (resizeClient) /* initiate NCCalcSize to resize client wnd */
459         SetWindowPos(hwnd, 0,0,0,0,0, 
460                      SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
461                      SWP_NOZORDER | SWP_NOACTIVATE);
462
463     if (repaintBtns)
464         SendMessageA(hwnd, WM_NCPAINT, 0, 0); 
465 }
466
467 static LRESULT  
468 PAGER_SetPos(HWND hwnd, INT newPos, BOOL fromBtnPress)
469 {
470     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
471     INT scrollRange = PAGER_GetScrollRange(hwnd, infoPtr);
472
473     if ((scrollRange <= 0) || (newPos < 0))
474         infoPtr->nPos = 0;
475     else if (newPos > scrollRange)
476         infoPtr->nPos = scrollRange;
477     else
478         infoPtr->nPos = newPos;
479
480     TRACE("[%04x] pos=%d\n", hwnd, infoPtr->nPos);
481
482     /* gray and restore btns, and if from WM_SETPOS, hide the gray btns */
483     PAGER_UpdateBtns(hwnd, infoPtr, scrollRange, !fromBtnPress);
484     PAGER_PositionChildWnd(hwnd, infoPtr);
485
486     return 0;
487 }
488
489 static LRESULT
490 PAGER_HandleWindowPosChanging(HWND hwnd, WINDOWPOS *winpos)
491 {
492     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
493
494     if (infoPtr->bNoResize && !(winpos->flags & SWP_NOSIZE))
495     {
496         /* don't let the app resize the nonscrollable dimension of a control
497          * that was created with CCS_NORESIZE style
498          * (i.e. height for a horizontal pager, or width for a vertical one) */
499
500         if (PAGER_IsHorizontal(hwnd))
501             winpos->cy = infoPtr->nHeight;
502         else
503             winpos->cx = infoPtr->nWidth;
504     }
505
506     return 0;
507 }
508
509 static void 
510 PAGER_SetFixedWidth(HWND hwnd, PAGER_INFO* infoPtr)
511 {
512   /* Must set the non-scrollable dimension to be less than the full height/width
513    * so that NCCalcSize is called.  The Msoft docs mention 3/4 factor for button
514    * size, and experimentation shows that affect is almost right. */
515
516     RECT wndRect;
517     INT delta, h;
518     GetWindowRect(hwnd, &wndRect);
519
520     /* see what the app says for btn width */
521     PAGER_CalcSize(hwnd, &infoPtr->nWidth, TRUE);
522
523     if (infoPtr->bNoResize)
524     {
525         delta = wndRect.right - wndRect.left - infoPtr->nWidth;
526         if (delta > infoPtr->nButtonSize)
527             infoPtr->nWidth += 4 * infoPtr->nButtonSize / 3;
528         else if (delta > 0)
529             infoPtr->nWidth +=  infoPtr->nButtonSize / 3;
530     }
531
532     h = wndRect.bottom - wndRect.top + infoPtr->nButtonSize;
533
534     /* adjust non-scrollable dimension to fit the child */
535     SetWindowPos(hwnd, 0, 0,0, infoPtr->nWidth, h, 
536                  SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOZORDER);
537
538
539     TRACE("[%04x] infoPtr->nWidth set to %d\n",
540                hwnd, infoPtr->nWidth);
541 }
542
543 static void 
544 PAGER_SetFixedHeight(HWND hwnd, PAGER_INFO* infoPtr)
545 {
546   /* Must set the non-scrollable dimension to be less than the full height/width
547    * so that NCCalcSize is called.  The Msoft docs mention 3/4 factor for button
548    * size, and experimentation shows that affect is almost right. */
549
550     RECT wndRect;
551     INT delta, w;
552     GetWindowRect(hwnd, &wndRect);
553
554     /* see what the app says for btn height */
555     PAGER_CalcSize(hwnd, &infoPtr->nHeight, FALSE);
556
557     if (infoPtr->bNoResize)
558     {
559         delta = wndRect.bottom - wndRect.top - infoPtr->nHeight;
560         if (delta > infoPtr->nButtonSize)
561             infoPtr->nHeight += 4 * infoPtr->nButtonSize / 3;
562         else if (delta > 0)
563             infoPtr->nHeight +=  infoPtr->nButtonSize / 3;
564     }
565
566     w = wndRect.right - wndRect.left + infoPtr->nButtonSize;
567
568     /* adjust non-scrollable dimension to fit the child */
569     SetWindowPos(hwnd, 0, 0,0, w, infoPtr->nHeight, 
570                  SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOZORDER);
571
572     TRACE("[%04x] infoPtr->nHeight set to %d\n",
573                hwnd, infoPtr->nHeight);
574 }
575
576 static LRESULT
577 PAGER_RecalcSize(HWND hwnd)
578 {
579     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
580
581     TRACE("[%04x]\n", hwnd);
582
583     if (infoPtr->hwndChild)
584     {
585         INT scrollRange = PAGER_GetScrollRange(hwnd, infoPtr);
586
587         if (scrollRange <= 0)
588             PAGER_SetPos(hwnd, 0, FALSE);
589         else 
590         {
591             PAGER_UpdateBtns(hwnd, infoPtr, scrollRange, TRUE);
592             PAGER_PositionChildWnd(hwnd, infoPtr);
593         }
594     }
595
596     return 1;
597 }
598
599
600 static LRESULT
601 PAGER_SetBkColor (HWND hwnd, WPARAM wParam, LPARAM lParam)
602 {
603     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
604     COLORREF clrTemp = infoPtr->clrBk;
605
606     infoPtr->clrBk = (COLORREF)lParam;
607     TRACE("[%04x] %06lx\n", hwnd, infoPtr->clrBk);
608
609     PAGER_RecalcSize(hwnd);
610     SendMessageA(hwnd, WM_NCPAINT, (WPARAM)0, (LPARAM)0);
611
612     return (LRESULT)clrTemp;
613 }
614
615
616 static LRESULT
617 PAGER_SetBorder (HWND hwnd, WPARAM wParam, LPARAM lParam)
618 {
619     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
620     INT nTemp = infoPtr->nBorder;
621
622     infoPtr->nBorder = (INT)lParam;
623     TRACE("[%04x] %d\n", hwnd, infoPtr->nBorder);
624
625     PAGER_RecalcSize(hwnd);
626
627     return (LRESULT)nTemp;
628 }
629
630
631 static LRESULT
632 PAGER_SetButtonSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
633 {
634     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
635     INT nTemp = infoPtr->nButtonSize;
636
637     infoPtr->nButtonSize = (INT)lParam;
638     TRACE("[%04x] %d\n", hwnd, infoPtr->nButtonSize);
639
640     PAGER_RecalcSize(hwnd);
641
642     return (LRESULT)nTemp;
643 }
644
645
646 static LRESULT
647 PAGER_SetChild (HWND hwnd, WPARAM wParam, LPARAM lParam)
648 {
649     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
650
651     infoPtr->hwndChild = IsWindow ((HWND)lParam) ? (HWND)lParam : 0;
652
653     if (infoPtr->hwndChild)
654     {
655         TRACE("[%04x] hwndChild=%04x\n", hwnd, infoPtr->hwndChild);
656
657         if (PAGER_IsHorizontal(hwnd))
658             PAGER_SetFixedHeight(hwnd, infoPtr);
659         else
660             PAGER_SetFixedWidth(hwnd, infoPtr);
661
662         /* position child within the page scroller */
663         SetWindowPos(infoPtr->hwndChild, HWND_TOP,
664                      0,0,0,0,
665                      SWP_SHOWWINDOW | SWP_NOSIZE);
666
667         PAGER_SetPos(hwnd, 0, FALSE);
668     }
669
670     return 0;
671 }
672
673 static void
674 PAGER_Scroll(HWND hwnd, INT dir)
675 {
676     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
677     NMPGSCROLL nmpgScroll;
678     RECT rcWnd;
679
680     if (infoPtr->hwndChild)
681     {
682         ZeroMemory (&nmpgScroll, sizeof (NMPGSCROLL));
683         nmpgScroll.hdr.hwndFrom = hwnd;
684         nmpgScroll.hdr.idFrom   = GetWindowLongA (hwnd, GWL_ID);
685         nmpgScroll.hdr.code = PGN_SCROLL;
686
687         GetWindowRect(hwnd, &rcWnd);  
688         GetClientRect(hwnd, &nmpgScroll.rcParent);  
689         nmpgScroll.iXpos = nmpgScroll.iYpos = 0;
690         nmpgScroll.iDir = dir;
691
692         if (PAGER_IsHorizontal(hwnd))
693         {
694             nmpgScroll.iScroll = rcWnd.right - rcWnd.left;
695             nmpgScroll.iXpos = infoPtr->nPos;
696         }
697         else
698         {
699             nmpgScroll.iScroll = rcWnd.bottom - rcWnd.top;
700             nmpgScroll.iYpos = infoPtr->nPos;
701         }
702         nmpgScroll.iScroll -= 2*infoPtr->nButtonSize;
703   
704         SendMessageA (hwnd, WM_NOTIFY,
705                     (WPARAM)nmpgScroll.hdr.idFrom, (LPARAM)&nmpgScroll);
706   
707         TRACE("[%04x] PGN_SCROLL returns iScroll=%d\n", hwnd, nmpgScroll.iScroll);
708
709         if (nmpgScroll.iScroll > 0)
710         {
711             if (dir == PGF_SCROLLLEFT || dir == PGF_SCROLLUP)
712                 PAGER_SetPos(hwnd, infoPtr->nPos - nmpgScroll.iScroll, TRUE);
713             else
714                 PAGER_SetPos(hwnd, infoPtr->nPos + nmpgScroll.iScroll, TRUE);
715         }
716     }
717 }
718
719 static LRESULT
720 PAGER_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
721 {
722     PAGER_INFO *infoPtr;
723     DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
724
725     /* allocate memory for info structure */
726     infoPtr = (PAGER_INFO *)COMCTL32_Alloc (sizeof(PAGER_INFO));
727     SetWindowLongA (hwnd, 0, (DWORD)infoPtr);
728
729     /* set default settings */
730     infoPtr->hwndChild = (HWND)NULL;
731     infoPtr->bNoResize = dwStyle & CCS_NORESIZE;
732     infoPtr->clrBk = GetSysColor(COLOR_BTNFACE);
733     infoPtr->nBorder = 0;
734     infoPtr->nButtonSize = 12;
735     infoPtr->nPos = 0;
736     infoPtr->nWidth = 0;
737     infoPtr->nHeight = 0;
738     infoPtr->bForward = FALSE;
739     infoPtr->TLbtnState = PGF_INVISIBLE;
740     infoPtr->BRbtnState = PGF_INVISIBLE;
741
742     if (dwStyle & PGS_AUTOSCROLL)
743         FIXME("[%04x] Autoscroll style is not implemented yet.\n", hwnd);
744     if (dwStyle & PGS_DRAGNDROP)
745         FIXME("[%04x] Drag and Drop style is not implemented yet.\n", hwnd);
746     /*
747          * If neither horizontal nor vertical style specified, default to vertical.
748          * This is probably not necessary, since the style may be set later on as
749          * the control is initialized, but just in case it isn't, set it here.
750          */
751     if (!(dwStyle & PGS_HORZ) && !(dwStyle & PGS_VERT))
752     {
753         dwStyle |= PGS_VERT;
754         SetWindowLongA(hwnd, GWL_STYLE, dwStyle);
755     }
756
757     return 0;
758 }
759
760
761 static LRESULT
762 PAGER_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
763 {
764     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
765     /* free pager info data */
766     COMCTL32_Free (infoPtr);
767     SetWindowLongA (hwnd, 0, 0);
768     return 0;
769 }
770
771 static LRESULT
772 PAGER_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
773 {
774     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
775     LPRECT lpRect = (LPRECT)lParam;
776     /*
777      * lParam points to a RECT struct.  On entry, the struct
778      * contains the proposed wnd rectangle for the window. 
779      * On exit, the struct should contain the screen
780      * coordinates of the corresponding window's client area.
781      */
782         
783     if (PAGER_IsHorizontal(hwnd))
784     {
785         if (infoPtr->TLbtnState) /* != PGF_INVISIBLE */
786             lpRect->left += infoPtr->nButtonSize;
787         if (infoPtr->BRbtnState) 
788             lpRect->right -= infoPtr->nButtonSize;
789     }
790     else
791     {
792         if (infoPtr->TLbtnState)
793             lpRect->top += infoPtr->nButtonSize;
794         if (infoPtr->BRbtnState)
795             lpRect->bottom -= infoPtr->nButtonSize;
796     }
797
798     TRACE("[%04x] client rect set to %dx%d at (%d,%d)\n", hwnd,
799                 lpRect->right-lpRect->left,
800                 lpRect->bottom-lpRect->top,
801                 lpRect->left, lpRect->top);
802
803     return 0;
804 }
805
806 static LRESULT
807 PAGER_NCPaint (HWND hwnd, WPARAM wParam, LPARAM lParam)
808 {
809     PAGER_INFO* infoPtr = PAGER_GetInfoPtr(hwnd);
810     DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
811     RECT rcWindow, rcBottomRight, rcTopLeft;
812     HDC hdc;
813     BOOL bHorizontal = PAGER_IsHorizontal(hwnd);
814
815     if (dwStyle & WS_MINIMIZE)
816         return 0;
817
818     DefWindowProcA (hwnd, WM_NCPAINT, wParam, lParam);
819
820     if (!(hdc = GetDCEx (hwnd, 0, DCX_USESTYLE | DCX_WINDOW)))
821         return 0;
822
823     GetWindowRect (hwnd, &rcWindow);
824     OffsetRect (&rcWindow, -rcWindow.left, -rcWindow.top);
825
826     rcTopLeft = rcBottomRight = rcWindow;
827     if (bHorizontal)
828     {
829         rcTopLeft.right = rcTopLeft.left + infoPtr->nButtonSize;
830         rcBottomRight.left = rcBottomRight.right - infoPtr->nButtonSize;
831     }
832     else
833     {
834         rcTopLeft.bottom = rcTopLeft.top + infoPtr->nButtonSize;
835         rcBottomRight.top = rcBottomRight.bottom - infoPtr->nButtonSize;
836     }
837
838     PAGER_DrawButton(hdc, infoPtr->clrBk, rcTopLeft,
839                      bHorizontal, TRUE, infoPtr->TLbtnState);
840     PAGER_DrawButton(hdc, infoPtr->clrBk, rcBottomRight,
841                      bHorizontal, FALSE, infoPtr->BRbtnState); 
842
843     ReleaseDC( hwnd, hdc );
844     return 0;
845 }
846
847 static INT 
848 PAGER_HitTest (HWND hwnd, LPPOINT pt)
849 {
850     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
851     RECT clientRect;
852     BOOL bHorizontal = PAGER_IsHorizontal(hwnd);
853
854     GetClientRect (hwnd, &clientRect);
855
856     if (PtInRect(&clientRect, *pt))
857     {
858        /* TRACE("HTCLIENT\n"); */
859         return HTCLIENT;
860     }
861
862     if (infoPtr->TLbtnState && infoPtr->TLbtnState != PGF_GRAYED)
863     {
864         if (bHorizontal)
865         {
866             if (pt->x < clientRect.left)
867             {
868                 /* TRACE("HTLEFT\n"); */
869                 return HTLEFT;
870             }
871         }
872         else
873         {
874             if (pt->y < clientRect.top)
875             {
876                 /* TRACE("HTTOP\n"); */
877                 return HTTOP;
878             }
879         }
880     }
881
882     if (infoPtr->BRbtnState && infoPtr->BRbtnState != PGF_GRAYED)
883     {
884         if (bHorizontal)
885         {
886             if (pt->x > clientRect.right)
887             {
888                 /* TRACE("HTRIGHT\n"); */
889                 return HTRIGHT;
890             }
891         }
892         else
893         {
894             if (pt->y > clientRect.bottom)
895             {
896                /* TRACE("HTBOTTOM\n"); */
897                 return HTBOTTOM;
898             }
899         }
900     }
901
902     /* TRACE("HTNOWHERE\n"); */
903     return HTNOWHERE;
904 }
905
906 static LRESULT
907 PAGER_NCHitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
908 {
909     POINT pt = { SLOWORD(lParam), SHIWORD(lParam) };
910     ScreenToClient (hwnd, &pt);
911     return PAGER_HitTest(hwnd, &pt);
912 }
913
914 static LRESULT
915 PAGER_SetCursor( HWND hwnd, WPARAM wParam, LPARAM lParam )
916 {
917     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
918     BOOL notCaptured = FALSE;
919
920     switch(LOWORD(lParam))
921     {
922         case HTLEFT:
923         case HTTOP:
924             if ((notCaptured = infoPtr->TLbtnState != PGF_HOT))
925                 infoPtr->TLbtnState = PGF_HOT;
926             break;
927         case HTRIGHT:
928         case HTBOTTOM:
929             if ((notCaptured = infoPtr->BRbtnState != PGF_HOT))
930                infoPtr->BRbtnState = PGF_HOT;
931             break;
932         default:
933             return FALSE;
934     }
935
936     if (notCaptured)
937     {
938         TRACKMOUSEEVENT trackinfo;
939
940         TRACE("[%04x] SetCapture\n", hwnd);
941         SetCapture(hwnd);
942
943         trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
944         trackinfo.dwFlags = TME_QUERY;
945         trackinfo.hwndTrack = hwnd;
946         trackinfo.dwHoverTime = HOVER_DEFAULT;
947
948         /* call _TrackMouseEvent to see if we are currently tracking for this hwnd */
949         _TrackMouseEvent(&trackinfo);
950
951         /* Make sure tracking is enabled so we receive a WM_MOUSELEAVE message */
952         if(!(trackinfo.dwFlags & TME_LEAVE)) {
953             trackinfo.dwFlags = TME_LEAVE; /* notify upon leaving */
954  
955            /* call TRACKMOUSEEVENT so we receive a WM_MOUSELEAVE message */
956            /* and can properly deactivate the hot button */
957            _TrackMouseEvent(&trackinfo);
958         }
959
960         SendMessageA(hwnd, WM_NCPAINT, 0, 0); 
961     }
962
963     return TRUE;
964 }
965
966 static LRESULT
967 PAGER_MouseLeave (HWND hwnd, WPARAM wParam, LPARAM lParam)
968 {
969     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
970
971     TRACE("[%04x] ReleaseCapture\n", hwnd);
972     ReleaseCapture();
973
974     /* Notify parent of released mouse capture */
975     {
976         NMHDR nmhdr;
977         ZeroMemory (&nmhdr, sizeof (NMHDR));
978         nmhdr.hwndFrom = hwnd;
979         nmhdr.idFrom   = GetWindowLongA (hwnd, GWL_ID);
980         nmhdr.code = NM_RELEASEDCAPTURE;
981         SendMessageA (GetParent(hwnd), WM_NOTIFY,
982                         (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
983     }
984
985     /* make HOT btns NORMAL and hide gray btns */
986     PAGER_UpdateBtns(hwnd, infoPtr, -1, TRUE);
987
988     return TRUE;
989 }
990
991 static LRESULT
992 PAGER_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
993 {
994     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
995     BOOL repaintBtns = FALSE;
996     POINT pt = { SLOWORD(lParam), SHIWORD(lParam) };
997     INT hit;
998
999     TRACE("[%04x]\n", hwnd);
1000         
1001     hit = PAGER_HitTest(hwnd, &pt);
1002
1003     /* put btn in DEPRESSED state */
1004     if (hit == HTLEFT || hit == HTTOP)
1005     {
1006         repaintBtns = infoPtr->TLbtnState != PGF_DEPRESSED;
1007         infoPtr->TLbtnState = PGF_DEPRESSED;
1008     }
1009     else if (hit == HTRIGHT || hit == HTBOTTOM)
1010     {
1011         repaintBtns = infoPtr->BRbtnState != PGF_DEPRESSED;
1012         infoPtr->BRbtnState = PGF_DEPRESSED;
1013     }
1014
1015     if (repaintBtns)
1016         SendMessageA(hwnd, WM_NCPAINT, 0, 0); 
1017
1018     switch(hit)
1019     {
1020     case HTLEFT:
1021         TRACE("[%04x] PGF_SCROLLLEFT\n", hwnd);
1022         PAGER_Scroll(hwnd, PGF_SCROLLLEFT);
1023         break;
1024     case HTTOP:
1025         TRACE("[%04x] PGF_SCROLLUP\n", hwnd);
1026         PAGER_Scroll(hwnd, PGF_SCROLLUP);
1027         break;
1028     case HTRIGHT:
1029         TRACE("[%04x] PGF_SCROLLRIGHT\n", hwnd);
1030         PAGER_Scroll(hwnd, PGF_SCROLLRIGHT);
1031         break;
1032     case HTBOTTOM:
1033         TRACE("[%04x] PGF_SCROLLDOWN\n", hwnd);
1034         PAGER_Scroll(hwnd, PGF_SCROLLDOWN);
1035         break;
1036     default:
1037         break;
1038     }
1039
1040     return TRUE;
1041 }
1042
1043 static LRESULT
1044 PAGER_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
1045 {
1046     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1047     TRACE("[%04x]\n", hwnd);
1048
1049     /* make PRESSED btns NORMAL but don't hide gray btns */
1050     PAGER_UpdateBtns(hwnd, infoPtr, -1, FALSE);
1051
1052     return 0;
1053 }
1054
1055 static LRESULT
1056 PAGER_EraseBackground (HWND hwnd, WPARAM wParam, LPARAM lParam)
1057 {
1058     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1059     HBRUSH hBrush = CreateSolidBrush(infoPtr->clrBk);
1060     RECT rect;
1061
1062     GetClientRect (hwnd, &rect);
1063     FillRect ((HDC)wParam, &rect, hBrush);
1064
1065     /* background color of the child should be the same as the pager */
1066     if (infoPtr->hwndChild)
1067     {
1068         GetClientRect (infoPtr->hwndChild, &rect);
1069         FillRect ((HDC)wParam, &rect, hBrush);
1070     }
1071
1072     DeleteObject (hBrush);
1073     return TRUE;
1074 }
1075
1076
1077 static LRESULT
1078 PAGER_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
1079 {
1080     /* note that WM_SIZE is sent whenever NCCalcSize resizes the client wnd */
1081
1082     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1083     TRACE("[%04x] %dx%d\n", hwnd, LOWORD(lParam), HIWORD(lParam));
1084
1085     if (PAGER_IsHorizontal(hwnd))
1086         infoPtr->nHeight = HIWORD(lParam);
1087     else
1088         infoPtr->nWidth = LOWORD(lParam);
1089
1090     return PAGER_RecalcSize(hwnd);
1091 }
1092
1093
1094 static LRESULT WINAPI
1095 PAGER_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1096 {
1097     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1098
1099     if (!infoPtr && (uMsg != WM_CREATE))
1100         return DefWindowProcA (hwnd, uMsg, wParam, lParam);
1101
1102     switch (uMsg)
1103     {
1104         case PGM_FORWARDMOUSE:
1105             return PAGER_ForwardMouse (hwnd, wParam);
1106
1107         case PGM_GETBKCOLOR:
1108             return PAGER_GetBkColor(hwnd);
1109
1110         case PGM_GETBORDER:
1111             return PAGER_GetBorder(hwnd);
1112
1113         case PGM_GETBUTTONSIZE:
1114             return PAGER_GetButtonSize(hwnd);
1115
1116         case PGM_GETPOS:
1117             return PAGER_GetPos(hwnd);
1118
1119         case PGM_GETBUTTONSTATE:
1120             return PAGER_GetButtonState (hwnd, wParam, lParam);
1121
1122 /*      case PGM_GETDROPTARGET: */
1123
1124         case PGM_RECALCSIZE:
1125             return PAGER_RecalcSize(hwnd);
1126     
1127         case PGM_SETBKCOLOR:
1128             return PAGER_SetBkColor (hwnd, wParam, lParam);
1129
1130         case PGM_SETBORDER:
1131             return PAGER_SetBorder (hwnd, wParam, lParam);
1132
1133         case PGM_SETBUTTONSIZE:
1134             return PAGER_SetButtonSize (hwnd, wParam, lParam);
1135
1136         case PGM_SETCHILD:
1137             return PAGER_SetChild (hwnd, wParam, lParam);
1138
1139         case PGM_SETPOS:
1140             return PAGER_SetPos(hwnd, (INT)lParam, FALSE);
1141
1142         case WM_CREATE:
1143             return PAGER_Create (hwnd, wParam, lParam);
1144
1145         case WM_DESTROY:
1146             return PAGER_Destroy (hwnd, wParam, lParam);
1147
1148         case WM_SIZE:
1149             return PAGER_Size (hwnd, wParam, lParam);
1150
1151         case WM_NCPAINT:
1152             return PAGER_NCPaint (hwnd, wParam, lParam);
1153
1154         case WM_WINDOWPOSCHANGING:
1155             return PAGER_HandleWindowPosChanging (hwnd, (WINDOWPOS*)lParam);
1156
1157         case WM_NCCALCSIZE:
1158             return PAGER_NCCalcSize (hwnd, wParam, lParam);
1159
1160         case WM_NCHITTEST:
1161             return PAGER_NCHitTest (hwnd, wParam, lParam);
1162
1163         case WM_SETCURSOR:
1164         {
1165             if (hwnd == (HWND)wParam)
1166                 return PAGER_SetCursor(hwnd, wParam, lParam);
1167             else /* its for the child */
1168                 return 0;
1169         }
1170
1171         case WM_MOUSEMOVE:
1172             if (infoPtr->bForward && infoPtr->hwndChild)
1173                 PostMessageA(infoPtr->hwndChild, WM_MOUSEMOVE, wParam, lParam);
1174             return TRUE;                        
1175
1176         case WM_MOUSELEAVE:
1177             return PAGER_MouseLeave (hwnd, wParam, lParam);     
1178
1179         case WM_LBUTTONDOWN:
1180             return PAGER_LButtonDown (hwnd, wParam, lParam);
1181
1182         case WM_LBUTTONUP:
1183             return PAGER_LButtonUp (hwnd, wParam, lParam);
1184
1185         case WM_ERASEBKGND:
1186             return PAGER_EraseBackground (hwnd, wParam, lParam);
1187 /*
1188         case WM_PAINT:
1189             return PAGER_Paint (hwnd, wParam); 
1190 */
1191         case WM_NOTIFY:
1192         case WM_COMMAND:
1193             return SendMessageA (GetParent (hwnd), uMsg, wParam, lParam);
1194
1195         default:
1196             return DefWindowProcA (hwnd, uMsg, wParam, lParam);
1197     }
1198
1199     return 0;
1200 }
1201
1202
1203 VOID
1204 PAGER_Register (void)
1205 {
1206     WNDCLASSA wndClass;
1207
1208     ZeroMemory (&wndClass, sizeof(WNDCLASSA));
1209     wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS;
1210     wndClass.lpfnWndProc   = (WNDPROC)PAGER_WindowProc;
1211     wndClass.cbClsExtra    = 0;
1212     wndClass.cbWndExtra    = sizeof(PAGER_INFO *);
1213     wndClass.hCursor       = LoadCursorA (0, IDC_ARROWA);
1214     wndClass.hbrBackground = 0;
1215     wndClass.lpszClassName = WC_PAGESCROLLERA;
1216  
1217     RegisterClassA (&wndClass);
1218 }
1219
1220
1221 VOID
1222 PAGER_Unregister (void)
1223 {
1224     UnregisterClassA (WC_PAGESCROLLERA, (HINSTANCE)NULL);
1225 }
1226