Handle WM_CHARs and pass them to TREEVIEW_ProcessLetterKeys. See also
[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     BOOL   bHorizontal;/* orientation of the control */
29     COLORREF clrBk;    /* background color */
30     INT    nBorder;    /* border size for the control */
31     INT    nButtonSize;/* size of the pager btns */
32     INT    nPos;       /* scroll position */
33     INT    nDelta;     /* scroll delta */
34     INT    nWidth;     /* from child wnd's response to PGN_CALCSIZE */
35     INT    nHeight;    /* from child wnd's response to PGN_CALCSIZE */ 
36     BOOL   bForward;   /* forward WM_MOUSEMOVE msgs to the contained wnd */
37     INT    TLbtnState; /* state of top or left btn */
38     INT    BRbtnState; /* state of bottom or right btn */
39
40 } PAGER_INFO;
41
42 #define PAGER_GetInfoPtr(hwnd) ((PAGER_INFO *)GetWindowLongA(hwnd, 0))
43
44 #define MIN_ARROW_WIDTH  8
45 #define MIN_ARROW_HEIGHT 5
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         int nPos = infoPtr->nPos;
300
301         /* compensate for a grayed btn, which will soon become invisible */
302         if (infoPtr->TLbtnState == PGF_GRAYED)
303             nPos += infoPtr->nButtonSize;
304
305         if (infoPtr->bHorizontal)
306         {
307             TRACE("[%04x] SWP %dx%d at (%d,%d)\n", hwnd,
308                          infoPtr->nWidth, infoPtr->nHeight,
309                          -nPos, 0);
310             SetWindowPos(infoPtr->hwndChild, 0,
311                          -nPos, 0,
312                          infoPtr->nWidth, infoPtr->nHeight,
313                          SWP_NOZORDER);
314         }
315         else
316         {
317             TRACE("[%04x] SWP %dx%d at (%d,%d)\n", hwnd, 
318                          infoPtr->nWidth, infoPtr->nHeight,
319                          0, -nPos);
320             SetWindowPos(infoPtr->hwndChild, 0,
321                          0, -nPos,
322                          infoPtr->nWidth, infoPtr->nHeight,
323                          SWP_NOZORDER);
324         }
325
326         InvalidateRect(infoPtr->hwndChild, NULL, FALSE);
327     }
328 }
329
330 static INT
331 PAGER_GetScrollRange(HWND hwnd, PAGER_INFO* infoPtr)
332 {
333     INT scrollRange = 0;
334
335     if (infoPtr->hwndChild)
336     {
337         INT wndSize, childSize;
338         RECT wndRect;
339         GetWindowRect(hwnd, &wndRect);
340
341         if (infoPtr->bHorizontal)
342         {
343             wndSize = wndRect.right - wndRect.left;
344             PAGER_CalcSize(hwnd, &infoPtr->nWidth, TRUE);
345             childSize = infoPtr->nWidth;
346         }
347         else
348         {
349             wndSize = wndRect.bottom - wndRect.top;
350             PAGER_CalcSize(hwnd, &infoPtr->nHeight, FALSE);
351             childSize = infoPtr->nHeight;
352         }
353
354         TRACE("childSize = %d,  wndSize = %d\n", childSize, wndSize);
355         if (childSize > wndSize)
356             scrollRange = childSize - wndSize + infoPtr->nButtonSize;
357     }
358
359     TRACE("[%04x] returns %d\n", hwnd, scrollRange);
360     return scrollRange;
361 }
362
363 static void 
364 PAGER_GrayAndRestoreBtns(PAGER_INFO* infoPtr, INT scrollRange,
365                          BOOL* needsResize, BOOL* needsRepaint)
366 {
367     if (infoPtr->nPos > 0)
368     {
369         *needsResize |= !infoPtr->TLbtnState; /* PGF_INVISIBLE */
370         if (infoPtr->TLbtnState != PGF_DEPRESSED)
371             infoPtr->TLbtnState = PGF_NORMAL;
372     }
373     else
374     {
375         *needsRepaint |= (infoPtr->TLbtnState != PGF_GRAYED);
376         infoPtr->TLbtnState = PGF_GRAYED;
377     }
378
379     if (scrollRange <= 0)
380     {
381         *needsRepaint |= (infoPtr->TLbtnState != PGF_GRAYED);
382         infoPtr->TLbtnState = PGF_GRAYED;
383         *needsRepaint |= (infoPtr->BRbtnState != PGF_GRAYED);
384         infoPtr->BRbtnState = PGF_GRAYED;
385     }
386     else if (infoPtr->nPos < scrollRange)
387     {
388         *needsResize |= !infoPtr->BRbtnState; /* PGF_INVISIBLE */
389         if (infoPtr->BRbtnState != PGF_DEPRESSED)
390             infoPtr->BRbtnState = PGF_NORMAL;
391     }
392     else
393     {
394         *needsRepaint |= (infoPtr->BRbtnState != PGF_GRAYED);
395         infoPtr->BRbtnState = PGF_GRAYED;
396     }
397 }
398
399
400 static void 
401 PAGER_NormalizeBtns(PAGER_INFO* infoPtr, BOOL* needsRepaint)
402 {
403     if (infoPtr->TLbtnState & (PGF_HOT | PGF_DEPRESSED))
404     {
405         infoPtr->TLbtnState = PGF_NORMAL;
406         *needsRepaint = TRUE;
407     }
408
409     if (infoPtr->BRbtnState & (PGF_HOT | PGF_DEPRESSED))
410     {
411         infoPtr->BRbtnState = PGF_NORMAL;
412         *needsRepaint = TRUE;
413     }
414 }
415
416 static void 
417 PAGER_HideGrayBtns(PAGER_INFO* infoPtr, BOOL* needsResize)
418 {
419     if (infoPtr->TLbtnState == PGF_GRAYED)
420     {
421         infoPtr->TLbtnState = PGF_INVISIBLE;
422         *needsResize = TRUE;
423     }
424
425     if (infoPtr->BRbtnState == PGF_GRAYED)
426     {
427         infoPtr->BRbtnState = PGF_INVISIBLE;
428         *needsResize = TRUE;
429     }
430 }
431
432 static void
433 PAGER_UpdateBtns(HWND hwnd, PAGER_INFO *infoPtr,
434                  INT scrollRange, BOOL hideGrayBtns)
435 {
436     BOOL resizeClient = FALSE;
437     BOOL repaintBtns = FALSE;
438
439     if (scrollRange < 0)
440         PAGER_NormalizeBtns(infoPtr, &repaintBtns);
441     else
442         PAGER_GrayAndRestoreBtns(infoPtr, scrollRange, &resizeClient, &repaintBtns);
443
444     if (hideGrayBtns)
445         PAGER_HideGrayBtns(infoPtr, &resizeClient);
446
447     if (resizeClient) /* initiate NCCalcSize to resize client wnd */
448         SetWindowPos(hwnd, 0,0,0,0,0, 
449                      SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
450                      SWP_NOZORDER | SWP_NOACTIVATE);
451
452     if (repaintBtns)
453         SendMessageA(hwnd, WM_NCPAINT, 0, 0); 
454 }
455
456 static LRESULT  
457 PAGER_SetPos(HWND hwnd, INT newPos, BOOL fromBtnPress)
458 {
459     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
460     INT scrollRange = PAGER_GetScrollRange(hwnd, infoPtr);
461
462     if ((scrollRange <= 0) || (newPos < 0))
463         infoPtr->nPos = 0;
464     else if (newPos > scrollRange)
465         infoPtr->nPos = scrollRange;
466     else
467         infoPtr->nPos = newPos;
468
469     TRACE("[%04x] pos=%d\n", hwnd, infoPtr->nPos);
470
471     /* gray and restore btns, and if from WM_SETPOS, hide the gray btns */
472     PAGER_UpdateBtns(hwnd, infoPtr, scrollRange, !fromBtnPress);
473     PAGER_PositionChildWnd(hwnd, infoPtr);
474
475     return 0;
476 }
477
478 static LRESULT
479 PAGER_HandleWindowPosChanging(HWND hwnd, WINDOWPOS *winpos)
480 {
481     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
482
483     if (infoPtr->bNoResize && !(winpos->flags & SWP_NOSIZE))
484     {
485         /* don't let the app resize the nonscrollable dimension of a control
486          * that was created with CCS_NORESIZE style
487          * (i.e. height for a horizontal pager, or width for a vertical one) */
488
489         if (infoPtr->bHorizontal)
490             winpos->cy = infoPtr->nHeight;
491         else
492             winpos->cx = infoPtr->nWidth;
493     }
494
495     return 0;
496 }
497
498 static void 
499 PAGER_SetFixedWidth(HWND hwnd, PAGER_INFO* infoPtr)
500 {
501   /* Must set the non-scrollable dimension to be less than the full height/width
502    * so that NCCalcSize is called.  The Msoft docs mention 3/4 factor for button
503    * size, and experimentation shows that affect is almost right. */
504
505     RECT wndRect;
506     INT delta;
507     GetWindowRect(hwnd, &wndRect);
508
509     /* see what the app says for btn width */
510     PAGER_CalcSize(hwnd, &infoPtr->nWidth, TRUE);
511
512     if (infoPtr->bNoResize)
513     {
514         delta = wndRect.right - wndRect.left - infoPtr->nWidth;
515         if (delta > infoPtr->nButtonSize)
516             infoPtr->nWidth += 4 * infoPtr->nButtonSize / 3;
517         else if (delta > 0)
518             infoPtr->nWidth +=  infoPtr->nButtonSize / 3;
519     }
520
521     infoPtr->nDelta = wndRect.bottom - wndRect.top - infoPtr->nButtonSize;
522
523     TRACE("[%04x] infoPtr->nWidth set to %d\n",
524                hwnd, infoPtr->nWidth);
525 }
526
527 static void 
528 PAGER_SetFixedHeight(HWND hwnd, PAGER_INFO* infoPtr)
529 {
530   /* Must set the non-scrollable dimension to be less than the full height/width
531    * so that NCCalcSize is called.  The Msoft docs mention 3/4 factor for button
532    * size, and experimentation shows that affect is almost right. */
533
534     RECT wndRect;
535     INT delta;
536     GetWindowRect(hwnd, &wndRect);
537
538     /* see what the app says for btn height */
539     PAGER_CalcSize(hwnd, &infoPtr->nHeight, FALSE);
540
541     if (infoPtr->bNoResize)
542     {
543         delta = wndRect.bottom - wndRect.top - infoPtr->nHeight;
544         if (delta > infoPtr->nButtonSize)
545             infoPtr->nHeight += 4 * infoPtr->nButtonSize / 3;
546         else if (delta > 0)
547             infoPtr->nHeight +=  infoPtr->nButtonSize / 3;
548     }
549
550     infoPtr->nDelta = wndRect.right - wndRect.left - infoPtr->nButtonSize;
551
552     TRACE("[%04x] infoPtr->nHeight set to %d\n",
553                hwnd, infoPtr->nHeight);
554 }
555
556 static LRESULT
557 PAGER_RecalcSize(HWND hwnd)
558 {
559     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
560     TRACE("[%04x]\n", hwnd);
561
562     if (infoPtr->hwndChild)
563     {
564         INT scrollRange = PAGER_GetScrollRange(hwnd, infoPtr);
565
566         if (scrollRange <= 0)
567             PAGER_SetPos(hwnd, 0, FALSE);
568         else 
569         {
570             PAGER_UpdateBtns(hwnd, infoPtr, scrollRange, TRUE);
571             PAGER_PositionChildWnd(hwnd, infoPtr);
572         }
573     }
574
575     return 1;
576 }
577
578
579 static LRESULT
580 PAGER_SetBkColor (HWND hwnd, WPARAM wParam, LPARAM lParam)
581 {
582     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
583     COLORREF clrTemp = infoPtr->clrBk;
584
585     infoPtr->clrBk = (COLORREF)lParam;
586     TRACE("[%04x] %06lx\n", hwnd, infoPtr->clrBk);
587
588     PAGER_RecalcSize(hwnd);
589
590     return (LRESULT)clrTemp;
591 }
592
593
594 static LRESULT
595 PAGER_SetBorder (HWND hwnd, WPARAM wParam, LPARAM lParam)
596 {
597     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
598     INT nTemp = infoPtr->nBorder;
599
600     infoPtr->nBorder = (INT)lParam;
601     TRACE("[%04x] %d\n", hwnd, infoPtr->nBorder);
602
603     PAGER_RecalcSize(hwnd);
604
605     return (LRESULT)nTemp;
606 }
607
608
609 static LRESULT
610 PAGER_SetButtonSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
611 {
612     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
613     INT nTemp = infoPtr->nButtonSize;
614
615     infoPtr->nButtonSize = (INT)lParam;
616     TRACE("[%04x] %d\n", hwnd, infoPtr->nButtonSize);
617
618     PAGER_RecalcSize(hwnd);
619
620     return (LRESULT)nTemp;
621 }
622
623
624 static LRESULT
625 PAGER_SetChild (HWND hwnd, WPARAM wParam, LPARAM lParam)
626 {
627     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
628
629     infoPtr->hwndChild = IsWindow ((HWND)lParam) ? (HWND)lParam : 0;
630
631     if (infoPtr->hwndChild)
632     {
633         TRACE("[%04x] hwndChild=%04x\n", hwnd, infoPtr->hwndChild);
634
635         if (infoPtr->bHorizontal)
636             PAGER_SetFixedHeight(hwnd, infoPtr);
637         else
638            PAGER_SetFixedWidth(hwnd, infoPtr);
639
640         /* adjust non-scrollable dimension to fit the child */
641         SetWindowPos(hwnd, 0, 0,0,
642            infoPtr->bHorizontal ? infoPtr->nDelta + infoPtr->nButtonSize : infoPtr->nWidth,
643            infoPtr->bHorizontal ? infoPtr->nHeight : infoPtr->nDelta + infoPtr->nButtonSize, 
644            SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOZORDER);
645
646         /* position child within the page scroller */
647         SetWindowPos(infoPtr->hwndChild, HWND_TOP,
648                      0,0,0,0,
649                      SWP_SHOWWINDOW | SWP_NOSIZE);
650
651         PAGER_SetPos(hwnd, 0, FALSE);
652     }
653
654     return 0;
655 }
656
657 static void
658 PAGER_Scroll(HWND hwnd, INT dir)
659 {
660     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
661     NMPGSCROLL nmpgScroll;
662
663     if (infoPtr->hwndChild)
664     {
665         BOOL res = FALSE;
666         ZeroMemory (&nmpgScroll, sizeof (NMPGSCROLL));
667         nmpgScroll.hdr.hwndFrom = hwnd;
668         nmpgScroll.hdr.idFrom   = GetWindowLongA (hwnd, GWL_ID);
669         nmpgScroll.hdr.code = PGN_SCROLL;
670
671         GetClientRect(hwnd, &nmpgScroll.rcParent);  
672         nmpgScroll.iXpos = nmpgScroll.iYpos = 0;
673         nmpgScroll.iDir = dir;
674         nmpgScroll.iScroll = infoPtr->nDelta;
675
676         if (infoPtr->bHorizontal)
677             nmpgScroll.iXpos = infoPtr->nPos;
678         else
679             nmpgScroll.iYpos = infoPtr->nPos;
680
681         TRACE("[%04x] sending PGN_SCROLL\n", hwnd);
682         res = SendMessageA (hwnd, WM_NOTIFY,
683                     (WPARAM)nmpgScroll.hdr.idFrom, (LPARAM)&nmpgScroll);
684
685         if (res && infoPtr->nDelta != nmpgScroll.iScroll)
686         {
687             TRACE("delta changing from %d to %d\n",
688             infoPtr->nDelta, nmpgScroll.iScroll);
689             infoPtr->nDelta = nmpgScroll.iScroll;
690         }
691
692         if (dir == PGF_SCROLLLEFT || dir == PGF_SCROLLUP)
693             PAGER_SetPos(hwnd, infoPtr->nPos - infoPtr->nDelta, TRUE);
694         else
695             PAGER_SetPos(hwnd, infoPtr->nPos + infoPtr->nDelta, TRUE);
696     }
697 }
698
699 static LRESULT
700 PAGER_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
701 {
702     PAGER_INFO *infoPtr;
703     DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
704     RECT rect;
705
706     SetWindowLongA(hwnd, GWL_STYLE, dwStyle & WS_CLIPCHILDREN);
707
708     /* allocate memory for info structure */
709     infoPtr = (PAGER_INFO *)COMCTL32_Alloc (sizeof(PAGER_INFO));
710     SetWindowLongA (hwnd, 0, (DWORD)infoPtr);
711
712     /* set default settings */
713     infoPtr->hwndChild = (HWND)NULL;
714     infoPtr->bNoResize = dwStyle & CCS_NORESIZE;
715     infoPtr->clrBk = GetSysColor(COLOR_BTNFACE);
716     infoPtr->nBorder = 0;
717     infoPtr->nButtonSize = 12;
718     infoPtr->nPos = 0;
719     infoPtr->nWidth = 0;
720     infoPtr->nHeight = 0;
721     infoPtr->bForward = FALSE;
722     infoPtr->TLbtnState = PGF_INVISIBLE;
723     infoPtr->BRbtnState = PGF_INVISIBLE;
724
725     if (dwStyle & PGS_AUTOSCROLL)
726         FIXME("[%04x] Autoscroll style is not implemented yet.\n", hwnd);
727     if (dwStyle & PGS_DRAGNDROP)
728         FIXME("[%04x] Drag and Drop style is not implemented yet.\n", hwnd);
729     if ((dwStyle & PGS_HORZ) && (dwStyle & PGS_VERT))
730     {
731         ERR("[%04x] Cannot have both horizontal and vertical styles.\n", hwnd);
732         ERR("[%04x] Defaulting to vertical.\n", hwnd);
733         dwStyle &= ~PGS_HORZ;
734     }
735     else if (!(dwStyle & PGS_HORZ) && !(dwStyle & PGS_VERT))
736         dwStyle |= PGS_VERT; /* the default according to MSDN */
737
738     infoPtr->bHorizontal = dwStyle & PGS_HORZ;
739
740     GetWindowRect(hwnd, &rect);
741     if (infoPtr->bHorizontal)
742     {
743         infoPtr->nHeight = rect.bottom - rect.top;
744         infoPtr->nDelta =  rect.right - rect.left - infoPtr->nButtonSize;
745         TRACE("height = %d %s\n", infoPtr->nHeight,
746                         infoPtr->bNoResize ? "CCS_NORESIZE" : "");
747     }
748     else
749     {
750         infoPtr->nWidth = rect.right - rect.left;
751         infoPtr->nDelta =  rect.bottom - rect.top - infoPtr->nButtonSize;
752         TRACE("width = %d %s\n", infoPtr->nWidth,
753                         infoPtr->bNoResize ? "CCS_NORESIZE" : "");
754     }
755
756     TRACE("[%04x] orientation = %s\n", hwnd,
757                   infoPtr->bHorizontal ? "PGS_HORZ" : "PGS_VERT");
758
759     return 0;
760 }
761
762
763 static LRESULT
764 PAGER_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
765 {
766     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
767     /* free pager info data */
768     COMCTL32_Free (infoPtr);
769     SetWindowLongA (hwnd, 0, 0);
770     return 0;
771 }
772
773 static LRESULT
774 PAGER_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
775 {
776     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
777     LPRECT lpRect = (LPRECT)lParam;
778     /*
779      * lParam points to a RECT struct.  On entry, the struct
780      * contains the proposed wnd rectangle for the window. 
781      * On exit, the struct should contain the screen
782      * coordinates of the corresponding window's client area.
783      */
784         
785     if (infoPtr->bHorizontal)
786     {
787         if (infoPtr->TLbtnState) /* != PGF_INVISIBLE */
788             lpRect->left += infoPtr->nButtonSize;
789         if (infoPtr->BRbtnState) 
790             lpRect->right -= infoPtr->nButtonSize;
791     }
792     else
793     {
794         if (infoPtr->TLbtnState)
795             lpRect->top += infoPtr->nButtonSize;
796         if (infoPtr->BRbtnState)
797             lpRect->bottom -= infoPtr->nButtonSize;
798     }
799
800     TRACE("[%04x] client rect set to %dx%d at (%d,%d)\n", hwnd,
801                 lpRect->right-lpRect->left,
802                 lpRect->bottom-lpRect->top,
803                 lpRect->left, lpRect->top);
804
805     return 0;
806 }
807
808 static LRESULT
809 PAGER_NCPaint (HWND hwnd, WPARAM wParam, LPARAM lParam)
810 {
811     PAGER_INFO* infoPtr = PAGER_GetInfoPtr(hwnd);
812     DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
813     RECT rcWindow, rcBottomRight, rcTopLeft;
814     HDC hdc;
815
816     if (dwStyle & WS_MINIMIZE)
817         return 0;
818
819     DefWindowProcA (hwnd, WM_NCPAINT, wParam, lParam);
820
821     if (!(hdc = GetDCEx (hwnd, 0, DCX_USESTYLE | DCX_WINDOW)))
822         return 0;
823
824     GetWindowRect (hwnd, &rcWindow);
825     OffsetRect (&rcWindow, -rcWindow.left, -rcWindow.top);
826
827     rcTopLeft = rcBottomRight = rcWindow;
828     if (infoPtr->bHorizontal)
829     {
830         rcTopLeft.right = rcTopLeft.left + infoPtr->nButtonSize;
831         rcBottomRight.left = rcBottomRight.right - infoPtr->nButtonSize;
832     }
833     else
834     {
835         rcTopLeft.bottom = rcTopLeft.top + infoPtr->nButtonSize;
836         rcBottomRight.top = rcBottomRight.bottom - infoPtr->nButtonSize;
837     }
838
839     PAGER_DrawButton(hdc, infoPtr->clrBk, rcTopLeft,
840                     infoPtr->bHorizontal, TRUE, infoPtr->TLbtnState);
841     PAGER_DrawButton(hdc, infoPtr->clrBk, rcBottomRight,
842                     infoPtr->bHorizontal, FALSE, infoPtr->BRbtnState); 
843
844     ReleaseDC( hwnd, hdc );
845     return 0;
846 }
847
848 static INT 
849 PAGER_HitTest (HWND hwnd, LPPOINT pt)
850 {
851     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
852     RECT clientRect;
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 (infoPtr->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 (infoPtr->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 recieve a WM_MOUSELEAVE message */
952         if(!(trackinfo.dwFlags & TME_LEAVE)) {
953             trackinfo.dwFlags = TME_LEAVE; /* notify upon leaving */
954  
955            /* call TRACKMOUSEEVENT so we recieve 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     if (infoPtr->hwndChild)
976     {
977         NMHDR nmhdr;
978         ZeroMemory (&nmhdr, sizeof (NMHDR));
979         nmhdr.hwndFrom = hwnd;
980         nmhdr.idFrom   = GetWindowLongA (hwnd, GWL_ID);
981         nmhdr.code = NM_RELEASEDCAPTURE;
982         SendMessageA (GetParent (infoPtr->hwndChild), WM_NOTIFY,
983                         (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
984     }
985
986     /* make HOT btns NORMAL and hide gray btns */
987     PAGER_UpdateBtns(hwnd, infoPtr, -1, TRUE);
988
989     return TRUE;
990 }
991
992 static LRESULT
993 PAGER_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
994 {
995     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
996     BOOL repaintBtns = FALSE;
997     POINT pt = { SLOWORD(lParam), SHIWORD(lParam) };
998     INT hit;
999
1000     TRACE("[%04x]\n", hwnd);
1001         
1002     if (infoPtr->nDelta <= 0)
1003         return FALSE;
1004
1005     hit = PAGER_HitTest(hwnd, &pt);
1006
1007     /* put btn in DEPRESSED state */
1008     if (hit == HTLEFT || hit == HTTOP)
1009     {
1010         repaintBtns = infoPtr->TLbtnState != PGF_DEPRESSED;
1011         infoPtr->TLbtnState = PGF_DEPRESSED;
1012     }
1013     else if (hit == HTRIGHT || hit == HTBOTTOM)
1014     {
1015         repaintBtns = infoPtr->BRbtnState != PGF_DEPRESSED;
1016         infoPtr->BRbtnState = PGF_DEPRESSED;
1017     }
1018
1019     if (repaintBtns)
1020         SendMessageA(hwnd, WM_NCPAINT, 0, 0); 
1021
1022     switch(hit)
1023     {
1024     case HTLEFT:
1025         TRACE("[%04x] PGF_SCROLLLEFT\n", hwnd);
1026         PAGER_Scroll(hwnd, PGF_SCROLLLEFT);
1027         break;
1028     case HTTOP:
1029         TRACE("[%04x] PGF_SCROLLUP\n", hwnd);
1030         PAGER_Scroll(hwnd, PGF_SCROLLUP);
1031         break;
1032     case HTRIGHT:
1033         TRACE("[%04x] PGF_SCROLLRIGHT\n", hwnd);
1034         PAGER_Scroll(hwnd, PGF_SCROLLRIGHT);
1035         break;
1036     case HTBOTTOM:
1037         TRACE("[%04x] PGF_SCROLLDOWN\n", hwnd);
1038         PAGER_Scroll(hwnd, PGF_SCROLLDOWN);
1039         break;
1040     default:
1041         break;
1042     }
1043
1044     return TRUE;
1045 }
1046
1047 static LRESULT
1048 PAGER_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
1049 {
1050     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1051     TRACE("[%04x]\n", hwnd);
1052
1053     /* make PRESSED btns NORMAL but don't hide gray btns */
1054     PAGER_UpdateBtns(hwnd, infoPtr, -1, FALSE);
1055
1056     return 0;
1057 }
1058
1059 static LRESULT
1060 PAGER_EraseBackground (HWND hwnd, WPARAM wParam, LPARAM lParam)
1061 {
1062     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1063     HBRUSH hBrush = CreateSolidBrush(infoPtr->clrBk);
1064     RECT rect;
1065     GetClientRect (hwnd, &rect);
1066
1067     FillRect ((HDC)wParam, &rect, hBrush);
1068     DeleteObject (hBrush);
1069     return TRUE;
1070 }
1071
1072
1073 static LRESULT
1074 PAGER_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
1075 {
1076     /* note that WM_SIZE is sent whenever NCCalcSize resizes the client wnd */
1077
1078     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1079     TRACE("[%04x] %dx%d\n", hwnd, LOWORD(lParam), HIWORD(lParam));
1080
1081     if (infoPtr->bHorizontal)
1082     {
1083         infoPtr->nHeight = HIWORD(lParam);
1084         infoPtr->nDelta =  LOWORD(lParam) - infoPtr->nButtonSize;
1085     }
1086     else
1087     {
1088         infoPtr->nWidth = LOWORD(lParam);
1089         infoPtr->nDelta = HIWORD(lParam) - infoPtr->nButtonSize;
1090     }
1091
1092     return PAGER_RecalcSize(hwnd);
1093 }
1094
1095
1096 static LRESULT WINAPI
1097 PAGER_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1098 {
1099     PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
1100
1101     if (!infoPtr && (uMsg != WM_CREATE))
1102         return DefWindowProcA (hwnd, uMsg, wParam, lParam);
1103
1104     switch (uMsg)
1105     {
1106         case PGM_FORWARDMOUSE:
1107             return PAGER_ForwardMouse (hwnd, wParam);
1108
1109         case PGM_GETBKCOLOR:
1110             return PAGER_GetBkColor(hwnd);
1111
1112         case PGM_GETBORDER:
1113             return PAGER_GetBorder(hwnd);
1114
1115         case PGM_GETBUTTONSIZE:
1116             return PAGER_GetButtonSize(hwnd);
1117
1118         case PGM_GETPOS:
1119             return PAGER_GetPos(hwnd);
1120
1121         case PGM_GETBUTTONSTATE:
1122             return PAGER_GetButtonState (hwnd, wParam, lParam);
1123
1124 /*      case PGM_GETDROPTARGET: */
1125
1126         case PGM_RECALCSIZE:
1127             return PAGER_RecalcSize(hwnd);
1128     
1129         case PGM_SETBKCOLOR:
1130             return PAGER_SetBkColor (hwnd, wParam, lParam);
1131
1132         case PGM_SETBORDER:
1133             return PAGER_SetBorder (hwnd, wParam, lParam);
1134
1135         case PGM_SETBUTTONSIZE:
1136             return PAGER_SetButtonSize (hwnd, wParam, lParam);
1137
1138         case PGM_SETCHILD:
1139             return PAGER_SetChild (hwnd, wParam, lParam);
1140
1141         case PGM_SETPOS:
1142             return PAGER_SetPos(hwnd, (INT)lParam, FALSE);
1143
1144         case WM_CREATE:
1145             return PAGER_Create (hwnd, wParam, lParam);
1146
1147         case WM_DESTROY:
1148             return PAGER_Destroy (hwnd, wParam, lParam);
1149
1150         case WM_SIZE:
1151             return PAGER_Size (hwnd, wParam, lParam);
1152
1153         case WM_NCPAINT:
1154             return PAGER_NCPaint (hwnd, wParam, lParam);
1155
1156         case WM_WINDOWPOSCHANGING:
1157             return PAGER_HandleWindowPosChanging (hwnd, (WINDOWPOS*)lParam);
1158
1159         case WM_NCCALCSIZE:
1160             return PAGER_NCCalcSize (hwnd, wParam, lParam);
1161
1162         case WM_NCHITTEST:
1163             return PAGER_NCHitTest (hwnd, wParam, lParam);
1164
1165         case WM_SETCURSOR:
1166         {
1167             if (hwnd == (HWND)wParam)
1168                 return PAGER_SetCursor(hwnd, wParam, lParam);
1169             else /* its for the child */
1170                 return 0;
1171         }
1172
1173         case WM_MOUSEMOVE:
1174             if (infoPtr->bForward && infoPtr->hwndChild)
1175                 PostMessageA(infoPtr->hwndChild, WM_MOUSEMOVE, wParam, lParam);
1176             return TRUE;                        
1177
1178         case WM_MOUSELEAVE:
1179             return PAGER_MouseLeave (hwnd, wParam, lParam);     
1180
1181         case WM_LBUTTONDOWN:
1182             return PAGER_LButtonDown (hwnd, wParam, lParam);
1183
1184         case WM_LBUTTONUP:
1185             return PAGER_LButtonUp (hwnd, wParam, lParam);
1186
1187         case WM_ERASEBKGND:
1188             return PAGER_EraseBackground (hwnd, wParam, lParam);
1189 /*
1190         case WM_PAINT:
1191             return PAGER_Paint (hwnd, wParam); 
1192 */
1193         case WM_NOTIFY:
1194         case WM_COMMAND:
1195             return SendMessageA (GetParent (hwnd), uMsg, wParam, lParam);
1196
1197         default:
1198             return DefWindowProcA (hwnd, uMsg, wParam, lParam);
1199     }
1200
1201     return 0;
1202 }
1203
1204
1205 VOID
1206 PAGER_Register (void)
1207 {
1208     WNDCLASSA wndClass;
1209
1210     ZeroMemory (&wndClass, sizeof(WNDCLASSA));
1211     wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS;
1212     wndClass.lpfnWndProc   = (WNDPROC)PAGER_WindowProc;
1213     wndClass.cbClsExtra    = 0;
1214     wndClass.cbWndExtra    = sizeof(PAGER_INFO *);
1215     wndClass.hCursor       = LoadCursorA (0, IDC_ARROWA);
1216     wndClass.hbrBackground = 0;
1217     wndClass.lpszClassName = WC_PAGESCROLLERA;
1218  
1219     RegisterClassA (&wndClass);
1220 }
1221
1222
1223 VOID
1224 PAGER_Unregister (void)
1225 {
1226     UnregisterClassA (WC_PAGESCROLLERA, (HINSTANCE)NULL);
1227 }
1228