Add NM_RCLICK notification support for toolbars.
[wine] / dlls / comctl32 / progress.c
1 /*
2  * Progress control
3  *
4  * Copyright 1997, 2002 Dimitrie O. Paun
5  * Copyright 1998, 1999 Eric Kohl
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * NOTE
22  * 
23  * This code was audited for completeness against the documented features
24  * of Comctl32.dll version 6.0 on Sep. 9, 2002, by Dimitrie O. Paun.
25  * 
26  * Unless otherwise noted, we believe this code to be complete, as per
27  * the specification mentioned above.
28  * If you discover missing features, or bugs, please note them below.
29  * 
30  * TODO
31  *   --support PBS_MARQUE
32  *
33  */
34
35 #include <stdarg.h>
36 #include <string.h>
37 #include "windef.h"
38 #include "winbase.h"
39 #include "wingdi.h"
40 #include "winuser.h"
41 #include "winnls.h"
42 #include "commctrl.h"
43 #include "comctl32.h"
44 #include "wine/debug.h"
45
46 WINE_DEFAULT_DEBUG_CHANNEL(progress);
47
48 typedef struct
49 {
50     HWND      Self;         /* The window handle for this control */
51     INT       CurVal;       /* Current progress value */
52     INT       MinVal;       /* Minimum progress value */
53     INT       MaxVal;       /* Maximum progress value */
54     INT       Step;         /* Step to use on PMB_STEPIT */
55     COLORREF  ColorBar;     /* Bar color */
56     COLORREF  ColorBk;      /* Background color */
57     HFONT     Font;         /* Handle to font (not unused) */
58 } PROGRESS_INFO;
59
60 /* Control configuration constants */
61
62 #define LED_GAP    2
63
64 /***********************************************************************
65  * PROGRESS_Invalidate
66  *
67  * Invalide the range between old and new pos.
68  */
69 static void PROGRESS_Invalidate( PROGRESS_INFO *infoPtr, INT old, INT new )
70 {
71     LONG style = GetWindowLongW (infoPtr->Self, GWL_STYLE);
72     RECT rect;
73     int oldPos, newPos, ledWidth;
74
75     GetClientRect (infoPtr->Self, &rect);
76     InflateRect(&rect, -1, -1);
77
78     if (style & PBS_VERTICAL)
79     {
80         oldPos = rect.bottom - MulDiv (old - infoPtr->MinVal, rect.bottom - rect.top,
81                                        infoPtr->MaxVal - infoPtr->MinVal);
82         newPos = rect.bottom - MulDiv (new - infoPtr->MinVal, rect.bottom - rect.top,
83                                        infoPtr->MaxVal - infoPtr->MinVal);
84         ledWidth = MulDiv (rect.right - rect.left, 2, 3);
85         rect.top = min( oldPos, newPos );
86         rect.bottom = max( oldPos, newPos );
87         if (!(style & PBS_SMOOTH)) rect.top -= ledWidth;
88         InvalidateRect( infoPtr->Self, &rect, oldPos < newPos );
89     }
90     else
91     {
92         oldPos = rect.left + MulDiv (old - infoPtr->MinVal, rect.right - rect.left,
93                                      infoPtr->MaxVal - infoPtr->MinVal);
94         newPos = rect.left + MulDiv (new - infoPtr->MinVal, rect.right - rect.left,
95                                      infoPtr->MaxVal - infoPtr->MinVal);
96         ledWidth = MulDiv (rect.bottom - rect.top, 2, 3);
97         rect.left = min( oldPos, newPos );
98         rect.right = max( oldPos, newPos );
99         if (!(style & PBS_SMOOTH)) rect.right += ledWidth;
100         InvalidateRect( infoPtr->Self, &rect, oldPos > newPos );
101     }
102 }
103
104
105 /***********************************************************************
106  * PROGRESS_Draw
107  * Draws the progress bar.
108  */
109 static LRESULT PROGRESS_Draw (PROGRESS_INFO *infoPtr, HDC hdc)
110 {
111     HBRUSH hbrBar, hbrBk;
112     int rightBar, rightMost, ledWidth;
113     RECT rect;
114     DWORD dwStyle;
115
116     TRACE("(infoPtr=%p, hdc=%p)\n", infoPtr, hdc);
117
118     /* get the required bar brush */
119     if (infoPtr->ColorBar == CLR_DEFAULT)
120         hbrBar = GetSysColorBrush(COLOR_HIGHLIGHT);
121     else
122         hbrBar = CreateSolidBrush (infoPtr->ColorBar);
123
124     if (infoPtr->ColorBk == CLR_DEFAULT)
125         hbrBk = GetSysColorBrush(COLOR_3DFACE);
126     else
127         hbrBk = CreateSolidBrush(infoPtr->ColorBk);
128
129     /* get client rectangle */
130     GetClientRect (infoPtr->Self, &rect);
131     FrameRect( hdc, &rect, hbrBk );
132     InflateRect(&rect, -1, -1);
133
134     /* get the window style */
135     dwStyle = GetWindowLongW (infoPtr->Self, GWL_STYLE);
136
137     /* compute extent of progress bar */
138     if (dwStyle & PBS_VERTICAL) {
139         rightBar  = rect.bottom -
140                     MulDiv (infoPtr->CurVal - infoPtr->MinVal,
141                             rect.bottom - rect.top,
142                             infoPtr->MaxVal - infoPtr->MinVal);
143         ledWidth  = MulDiv (rect.right - rect.left, 2, 3);
144         rightMost = rect.top;
145     } else {
146         rightBar = rect.left +
147                    MulDiv (infoPtr->CurVal - infoPtr->MinVal,
148                            rect.right - rect.left,
149                            infoPtr->MaxVal - infoPtr->MinVal);
150         ledWidth = MulDiv (rect.bottom - rect.top, 2, 3);
151         rightMost = rect.right;
152     }
153
154     /* now draw the bar */
155     if (dwStyle & PBS_SMOOTH)
156     {
157         if (dwStyle & PBS_VERTICAL)
158         {
159             INT old_top = rect.top;
160             rect.top = rightBar;
161             FillRect(hdc, &rect, hbrBar);
162             rect.bottom = rect.top;
163             rect.top = old_top;
164             FillRect(hdc, &rect, hbrBk);
165         }
166         else
167         {
168             INT old_right = rect.right;
169             rect.right = rightBar;
170             FillRect(hdc, &rect, hbrBar);
171             rect.left = rect.right;
172             rect.right = old_right;
173             FillRect(hdc, &rect, hbrBk);
174         }
175     } else {
176         if (dwStyle & PBS_VERTICAL) {
177             while(rect.bottom > rightBar) {
178                 rect.top = rect.bottom - ledWidth;
179                 if (rect.top < rightMost)
180                     rect.top = rightMost;
181                 FillRect(hdc, &rect, hbrBar);
182                 rect.bottom = rect.top;
183                 rect.top -= LED_GAP;
184                 if (rect.top <= rightBar) break;
185                 FillRect(hdc, &rect, hbrBk);
186                 rect.bottom = rect.top;
187             }
188             rect.top = rightMost;
189             FillRect(hdc, &rect, hbrBk);
190         } else {
191             while(rect.left < rightBar) {
192                 rect.right = rect.left + ledWidth;
193                 if (rect.right > rightMost)
194                     rect.right = rightMost;
195                 FillRect(hdc, &rect, hbrBar);
196                 rect.left = rect.right;
197                 rect.right += LED_GAP;
198                 if (rect.right >= rightBar) break;
199                 FillRect(hdc, &rect, hbrBk);
200                 rect.left = rect.right;
201             }
202             rect.right = rightMost;
203             FillRect(hdc, &rect, hbrBk);
204         }
205     }
206
207     /* delete bar brush */
208     if (infoPtr->ColorBar != CLR_DEFAULT) DeleteObject (hbrBar);
209     if (infoPtr->ColorBk != CLR_DEFAULT) DeleteObject (hbrBk);
210
211     return 0;
212 }
213
214
215 /***********************************************************************
216  * PROGRESS_Paint
217  * Draw the progress bar. The background need not be erased.
218  * If dc!=0, it draws on it
219  */
220 static LRESULT PROGRESS_Paint (PROGRESS_INFO *infoPtr, HDC hdc)
221 {
222     PAINTSTRUCT ps;
223     if (hdc) return PROGRESS_Draw (infoPtr, hdc);
224     hdc = BeginPaint (infoPtr->Self, &ps);
225     PROGRESS_Draw (infoPtr, hdc);
226     EndPaint (infoPtr->Self, &ps);
227     return 0;
228 }
229
230
231 /***********************************************************************
232  *           PROGRESS_CoercePos
233  * Makes sure the current position (CurVal) is within bounds.
234  */
235 static void PROGRESS_CoercePos(PROGRESS_INFO *infoPtr)
236 {
237     if(infoPtr->CurVal < infoPtr->MinVal)
238         infoPtr->CurVal = infoPtr->MinVal;
239     if(infoPtr->CurVal > infoPtr->MaxVal)
240         infoPtr->CurVal = infoPtr->MaxVal;
241 }
242
243
244 /***********************************************************************
245  *           PROGRESS_SetFont
246  * Set new Font for progress bar
247  */
248 static HFONT PROGRESS_SetFont (PROGRESS_INFO *infoPtr, HFONT hFont, BOOL bRedraw)
249 {
250     HFONT hOldFont = infoPtr->Font;
251     infoPtr->Font = hFont;
252     /* Since infoPtr->Font is not used, there is no need for repaint */
253     return hOldFont;
254 }
255
256 static DWORD PROGRESS_SetRange (PROGRESS_INFO *infoPtr, int low, int high)
257 {
258     DWORD res = MAKELONG(LOWORD(infoPtr->MinVal), LOWORD(infoPtr->MaxVal));
259
260     /* if nothing changes, simply return */
261     if(infoPtr->MinVal == low && infoPtr->MaxVal == high) return res;
262
263     infoPtr->MinVal = low;
264     infoPtr->MaxVal = high;
265     PROGRESS_CoercePos(infoPtr);
266     InvalidateRect(infoPtr->Self, NULL, TRUE);
267     return res;
268 }
269
270 /***********************************************************************
271  *           ProgressWindowProc
272  */
273 static LRESULT WINAPI ProgressWindowProc(HWND hwnd, UINT message,
274                                   WPARAM wParam, LPARAM lParam)
275 {
276     PROGRESS_INFO *infoPtr;
277
278     TRACE("hwnd=%p msg=%04x wparam=%x lParam=%lx\n", hwnd, message, wParam, lParam);
279
280     infoPtr = (PROGRESS_INFO *)GetWindowLongW(hwnd, 0);
281
282     if (!infoPtr && message != WM_CREATE)
283         return DefWindowProcW( hwnd, message, wParam, lParam );
284
285     switch(message) {
286     case WM_CREATE:
287     {
288         DWORD dwExStyle = GetWindowLongW (hwnd, GWL_EXSTYLE);
289         dwExStyle &= ~(WS_EX_CLIENTEDGE | WS_EX_WINDOWEDGE);
290         dwExStyle |= WS_EX_STATICEDGE;
291         SetWindowLongW (hwnd, GWL_EXSTYLE, dwExStyle);
292         /* Force recalculation of a non-client area */
293         SetWindowPos(hwnd, 0, 0, 0, 0, 0,
294             SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
295
296         /* allocate memory for info struct */
297         infoPtr = (PROGRESS_INFO *)Alloc (sizeof(PROGRESS_INFO));
298         if (!infoPtr) return -1;
299         SetWindowLongW (hwnd, 0, (DWORD)infoPtr);
300
301         /* initialize the info struct */
302         infoPtr->Self = hwnd;
303         infoPtr->MinVal = 0;
304         infoPtr->MaxVal = 100;
305         infoPtr->CurVal = 0;
306         infoPtr->Step = 10;
307         infoPtr->ColorBar = CLR_DEFAULT;
308         infoPtr->ColorBk = CLR_DEFAULT;
309         infoPtr->Font = 0;
310         TRACE("Progress Ctrl creation, hwnd=%p\n", hwnd);
311         return 0;
312     }
313
314     case WM_DESTROY:
315         TRACE("Progress Ctrl destruction, hwnd=%p\n", hwnd);
316         Free (infoPtr);
317         SetWindowLongW(hwnd, 0, 0);
318         return 0;
319
320     case WM_GETFONT:
321         return (LRESULT)infoPtr->Font;
322
323     case WM_SETFONT:
324         return (LRESULT)PROGRESS_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
325
326     case WM_PAINT:
327         return PROGRESS_Paint (infoPtr, (HDC)wParam);
328
329     case PBM_DELTAPOS:
330     {
331         INT oldVal;
332         oldVal = infoPtr->CurVal;
333         if(wParam != 0) {
334             infoPtr->CurVal += (INT)wParam;
335             PROGRESS_CoercePos (infoPtr);
336             TRACE("PBM_DELTAPOS: current pos changed from %d to %d\n", oldVal, infoPtr->CurVal);
337             PROGRESS_Invalidate( infoPtr, oldVal, infoPtr->CurVal );
338         }
339         return oldVal;
340     }
341
342     case PBM_SETPOS:
343     {
344         UINT oldVal;
345         oldVal = infoPtr->CurVal;
346         if(oldVal != wParam) {
347             infoPtr->CurVal = (INT)wParam;
348             PROGRESS_CoercePos(infoPtr);
349             TRACE("PBM_SETPOS: current pos changed from %d to %d\n", oldVal, infoPtr->CurVal);
350             PROGRESS_Invalidate( infoPtr, oldVal, infoPtr->CurVal );
351         }
352         return oldVal;
353     }
354
355     case PBM_SETRANGE:
356         return PROGRESS_SetRange (infoPtr, (int)LOWORD(lParam), (int)HIWORD(lParam));
357
358     case PBM_SETSTEP:
359     {
360         INT oldStep;
361         oldStep = infoPtr->Step;
362         infoPtr->Step = (INT)wParam;
363         return oldStep;
364     }
365
366     case PBM_STEPIT:
367     {
368         INT oldVal;
369         oldVal = infoPtr->CurVal;
370         infoPtr->CurVal += infoPtr->Step;
371         if(infoPtr->CurVal > infoPtr->MaxVal)
372             infoPtr->CurVal = infoPtr->MinVal;
373         if(oldVal != infoPtr->CurVal)
374         {
375             TRACE("PBM_STEPIT: current pos changed from %d to %d\n", oldVal, infoPtr->CurVal);
376             PROGRESS_Invalidate( infoPtr, oldVal, infoPtr->CurVal );
377         }
378         return oldVal;
379     }
380
381     case PBM_SETRANGE32:
382         return PROGRESS_SetRange (infoPtr, (int)wParam, (int)lParam);
383
384     case PBM_GETRANGE:
385         if (lParam) {
386             ((PPBRANGE)lParam)->iLow = infoPtr->MinVal;
387             ((PPBRANGE)lParam)->iHigh = infoPtr->MaxVal;
388         }
389         return wParam ? infoPtr->MinVal : infoPtr->MaxVal;
390
391     case PBM_GETPOS:
392         return infoPtr->CurVal;
393
394     case PBM_SETBARCOLOR:
395         infoPtr->ColorBar = (COLORREF)lParam;
396         InvalidateRect(hwnd, NULL, TRUE);
397         return 0;
398
399     case PBM_SETBKCOLOR:
400         infoPtr->ColorBk = (COLORREF)lParam;
401         InvalidateRect(hwnd, NULL, TRUE);
402         return 0;
403
404     default:
405         if ((message >= WM_USER) && (message < WM_APP))
406             ERR("unknown msg %04x wp=%04x lp=%08lx\n", message, wParam, lParam );
407         return DefWindowProcW( hwnd, message, wParam, lParam );
408     }
409 }
410
411
412 /***********************************************************************
413  * PROGRESS_Register [Internal]
414  *
415  * Registers the progress bar window class.
416  */
417 VOID PROGRESS_Register (void)
418 {
419     WNDCLASSW wndClass;
420
421     ZeroMemory (&wndClass, sizeof(wndClass));
422     wndClass.style         = CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
423     wndClass.lpfnWndProc   = (WNDPROC)ProgressWindowProc;
424     wndClass.cbClsExtra    = 0;
425     wndClass.cbWndExtra    = sizeof (PROGRESS_INFO *);
426     wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
427     wndClass.lpszClassName = PROGRESS_CLASSW;
428
429     RegisterClassW (&wndClass);
430 }
431
432
433 /***********************************************************************
434  * PROGRESS_Unregister [Internal]
435  *
436  * Unregisters the progress bar window class.
437  */
438 VOID PROGRESS_Unregister (void)
439 {
440     UnregisterClassW (PROGRESS_CLASSW, NULL);
441 }