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