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