1 |
/* wptBalloonPop.cpp - Balloon Popup-Box |
2 |
* Copyright (C) 2009 Timo Schulz |
3 |
* |
4 |
* This file is part of WinPT. |
5 |
* |
6 |
* WinPT is free software; you can redistribute it and/or |
7 |
* modify it under the terms of the GNU General Public License |
8 |
* as published by the Free Software Foundation; either version 2 |
9 |
* of the License, or (at your option) any later version. |
10 |
* |
11 |
* WinPT 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 |
* General Public License for more details. |
15 |
*/ |
16 |
|
17 |
|
18 |
/* The idea is to use a balloon style 'error message' which |
19 |
points to the control the error happened at. For instance |
20 |
if no valid E-Mail address was entered, the balloon message |
21 |
contains an error description with an arrow to the edit field |
22 |
the address was supposed to be entered in. */ |
23 |
|
24 |
#include <windows.h> |
25 |
#include <stdio.h> |
26 |
#include <time.h> |
27 |
|
28 |
#include "wptTypes.h" |
29 |
|
30 |
#define BT_WINDOWNAME "WinPT_BalloonPop" |
31 |
#define BT_TIMER_IDENTIFIER 23 |
32 |
#define BT_IOFFSET 8 |
33 |
|
34 |
|
35 |
extern HINSTANCE glob_hinst; |
36 |
|
37 |
struct balloon_ctx_s { |
38 |
LPCTSTR text; |
39 |
LPCTSTR icon_name; |
40 |
POINT pos; |
41 |
HWND hwnd; |
42 |
}; |
43 |
static struct balloon_ctx_s glob_msg; |
44 |
static BOOL window_class_registered = FALSE; |
45 |
|
46 |
|
47 |
static void |
48 |
display_balloon_text (HWND hwnd, HDC hdc, const char *text) |
49 |
{ |
50 |
RECT rc; |
51 |
memset (&rc, 0, sizeof (rc)); |
52 |
GetClientRect(hwnd, &rc); |
53 |
|
54 |
POINT pts[3]; |
55 |
pts[0].x = rc.left + BT_IOFFSET; |
56 |
pts[0].y = rc.top; |
57 |
pts[1].x = pts[0].x; |
58 |
pts[1].y = pts[0].y + BT_IOFFSET; |
59 |
pts[2].x = pts[1].x + BT_IOFFSET; |
60 |
pts[2].y = pts[1].y; |
61 |
|
62 |
// we merge three regions here: |
63 |
// 1) basis |
64 |
// 2) rounded box |
65 |
// 3) arrow |
66 |
HRGN hrgn = CreateRectRgn(0, 0, 0, 0); |
67 |
HRGN hrgn1 = CreateRoundRectRgn(rc.left, |
68 |
rc.top + BT_IOFFSET, |
69 |
rc.right, |
70 |
rc.bottom, |
71 |
15, 15); |
72 |
HRGN hrgn2 = CreatePolygonRgn(&pts[0], 3, ALTERNATE); |
73 |
CombineRgn(hrgn, hrgn1, hrgn2, RGN_OR); |
74 |
|
75 |
// fill the region and draw a frame around it |
76 |
FillRgn (hdc, hrgn, GetSysColorBrush(COLOR_INFOBK)); |
77 |
FrameRgn (hdc, hrgn, (HBRUSH)GetStockObject (DKGRAY_BRUSH), 1, 1); |
78 |
|
79 |
rc.top = rc.top + BT_IOFFSET*2; |
80 |
rc.bottom = rc.bottom - BT_IOFFSET; |
81 |
rc.left = rc.left + BT_IOFFSET; |
82 |
rc.right = rc.right - BT_IOFFSET; |
83 |
|
84 |
// FIXME: calculate best position |
85 |
HICON hicon = LoadIcon (NULL, glob_msg.icon_name); |
86 |
if (hicon != NULL) { |
87 |
DrawIconEx (hdc, 5, 20, hicon, 28, 28, 0, NULL, DI_NORMAL); |
88 |
DestroyIcon (hicon); |
89 |
} |
90 |
|
91 |
SetTextColor (hdc, GetSysColor (COLOR_INFOTEXT)); |
92 |
DrawText (hdc, text, strlen (text), &rc, DT_VCENTER + DT_NOCLIP); |
93 |
} |
94 |
|
95 |
|
96 |
// window procedure for the balloon tip. |
97 |
static LRESULT CALLBACK |
98 |
window_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) |
99 |
{ |
100 |
switch (msg) { |
101 |
case WM_PAINT: |
102 |
RECT rc; |
103 |
GetWindowRect (hwnd, &rc); |
104 |
|
105 |
POINT curpos; |
106 |
curpos.x = glob_msg.pos.x; |
107 |
curpos.y = glob_msg.pos.y; |
108 |
|
109 |
rc.right = curpos.x - BT_IOFFSET + 6 + rc.right - rc.left; |
110 |
rc.bottom = curpos.y + 20 + rc.bottom - rc.top; |
111 |
rc.left = curpos.x - BT_IOFFSET + 6; |
112 |
rc.top = curpos.y + 20; |
113 |
MoveWindow (hwnd, rc.left, rc.top, rc.right - rc.left, |
114 |
rc.bottom - rc.top, FALSE); |
115 |
|
116 |
HDC hdc; |
117 |
PAINTSTRUCT ps; |
118 |
hdc = BeginPaint (hwnd, &ps); |
119 |
|
120 |
LOGFONT lf; |
121 |
memset (&lf, 0, sizeof (lf)); |
122 |
lf.lfHeight = 13; |
123 |
lf.lfWeight = FW_NORMAL; |
124 |
lf.lfQuality = ANTIALIASED_QUALITY; |
125 |
strcpy (lf.lfFaceName, "MS Sans Serif"); |
126 |
|
127 |
HFONT hfont; |
128 |
hfont = CreateFontIndirect(&lf); |
129 |
HFONT hfont_old; |
130 |
hfont_old = (HFONT)SelectObject(ps.hdc, hfont); |
131 |
|
132 |
DrawText (ps.hdc, glob_msg.text, strlen (glob_msg.text), |
133 |
&rc, DT_VCENTER+DT_NOCLIP+DT_CALCRECT); |
134 |
rc.right = rc.right + 2*BT_IOFFSET; |
135 |
rc.bottom = rc.bottom + 3*BT_IOFFSET; |
136 |
|
137 |
ShowWindow (hwnd, SW_SHOWNA); |
138 |
MoveWindow(hwnd, rc.left, rc.top, rc.right - rc.left, |
139 |
rc.bottom - rc.top, TRUE); |
140 |
SetBkMode(ps.hdc, TRANSPARENT); |
141 |
display_balloon_text (hwnd, ps.hdc, glob_msg.text); |
142 |
|
143 |
// restore old font object |
144 |
SelectObject(ps.hdc, hfont_old); |
145 |
DeleteObject (hfont); |
146 |
EndPaint (hwnd, &ps); |
147 |
break; |
148 |
|
149 |
case WM_LBUTTONUP: |
150 |
ShowWindow (hwnd, SW_HIDE); |
151 |
break; |
152 |
|
153 |
default: |
154 |
return DefWindowProc (hwnd, msg, wparam, lparam); |
155 |
} |
156 |
|
157 |
return FALSE; |
158 |
} |
159 |
|
160 |
|
161 |
/* Callback procedure to hide the baloon window after the |
162 |
time has been expired */ |
163 |
static VOID CALLBACK |
164 |
popup_timer_proc (HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) |
165 |
{ |
166 |
ShowWindow (hwnd, SW_HIDE); |
167 |
} |
168 |
|
169 |
|
170 |
/* Register our new window class */ |
171 |
static void |
172 |
register_window_class (HINSTANCE hinst) |
173 |
{ |
174 |
WNDCLASS wclass; |
175 |
memset (&wclass, 0, sizeof (wclass)); |
176 |
wclass.style = CS_HREDRAW | CS_VREDRAW; |
177 |
wclass.lpfnWndProc = window_proc; |
178 |
wclass.hInstance = hinst; |
179 |
wclass.lpszClassName = BT_WINDOWNAME; |
180 |
|
181 |
if (!RegisterClass (&wclass)) |
182 |
abort (); |
183 |
} |
184 |
|
185 |
|
186 |
static LPCTSTR |
187 |
format_text (LPCTSTR string) |
188 |
{ |
189 |
const int off = 12; |
190 |
static char buf[2048]; |
191 |
int n = strlen (string), pos=0, i; |
192 |
|
193 |
memset (buf, 0, DIM (buf)); |
194 |
// FIXME: rewrite the function |
195 |
if (n > 1600) |
196 |
abort (); |
197 |
|
198 |
buf[pos++] = '\n'; |
199 |
for (i=0; i < off; i++) |
200 |
buf[pos++] = ' '; |
201 |
for (i=0; i < n; i++) { |
202 |
buf[pos++] = string[i]; |
203 |
if (string[i] == '\n') { |
204 |
for (int j=0; j < off; j++) |
205 |
buf[pos++] = ' '; |
206 |
} |
207 |
} |
208 |
buf[pos++] = '\n'; |
209 |
return buf; |
210 |
} |
211 |
|
212 |
|
213 |
/* Display a baloon message box at the given X- Y-coordinates */ |
214 |
HWND |
215 |
show_balloon_msg_pos (HWND hparwnd, int millis, int x, int y, |
216 |
LPCTSTR string, LPCTSTR icon_name) |
217 |
{ |
218 |
if (!window_class_registered) { |
219 |
register_window_class (glob_hinst); |
220 |
window_class_registered = TRUE; |
221 |
} |
222 |
|
223 |
// if no icon is requsted, print the text as it is. |
224 |
if (!icon_name) |
225 |
glob_msg.text = string; |
226 |
else |
227 |
glob_msg.text = format_text (string); |
228 |
glob_msg.icon_name = icon_name; |
229 |
|
230 |
if (x > 0 && y > 0) { // use specific coordinates |
231 |
glob_msg.pos.x = x; |
232 |
glob_msg.pos.y = y; |
233 |
} |
234 |
else { // point to the lower left region of the control |
235 |
RECT rect; |
236 |
GetWindowRect (hparwnd, &rect); |
237 |
glob_msg.pos.x = rect.left; |
238 |
glob_msg.pos.y = rect.bottom - 20; |
239 |
} |
240 |
|
241 |
// we use a persistent window for the balloon messages |
242 |
// instead of destroy/create we hide the window whenever it |
243 |
// is unused. |
244 |
if (!glob_msg.hwnd) { |
245 |
glob_msg.hwnd = CreateWindowEx (WS_EX_TOOLWINDOW | WS_EX_TOPMOST, |
246 |
BT_WINDOWNAME, BT_WINDOWNAME, |
247 |
WS_POPUP, 1, 1, 1, 1, |
248 |
NULL, NULL, glob_hinst, NULL); |
249 |
if (!glob_msg.hwnd) |
250 |
BUG (0); |
251 |
} |
252 |
|
253 |
// ensure 'parent' never loses focus. |
254 |
HWND hwnd = glob_msg.hwnd; |
255 |
ShowWindow (hwnd, SW_SHOW); |
256 |
UpdateWindow (hwnd); |
257 |
EnableWindow (hparwnd, TRUE); |
258 |
SetActiveWindow (NULL); |
259 |
|
260 |
// show message for a fixed amount of time |
261 |
SetTimer (hwnd, BT_TIMER_IDENTIFIER, millis, popup_timer_proc); |
262 |
|
263 |
return hwnd; |
264 |
} |
265 |
|
266 |
|
267 |
/* Show message at the default position for 5 seconds */ |
268 |
void |
269 |
show_balloon_msg (HWND hparwnd, LPCTSTR string, LPCTSTR icon_name) |
270 |
{ |
271 |
show_balloon_msg_pos (hparwnd, 5000, 0, 0, string ,icon_name); |
272 |
} |
273 |
|
274 |
|
275 |
/* Disable the message box (hide the window) */ |
276 |
void |
277 |
balloon_msg_disable (void) |
278 |
{ |
279 |
ShowWindow (glob_msg.hwnd, SW_HIDE); |
280 |
} |