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