OLD | NEW |
| (Empty) |
1 // Copyright 2005-2009 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // ======================================================================== | |
15 | |
16 #include "omaha/base/popup_menu.h" | |
17 | |
18 #include <windows.h> | |
19 #include "omaha/base/debug.h" | |
20 #include "omaha/base/error.h" | |
21 #include "omaha/base/logging.h" | |
22 #include "omaha/base/string.h" | |
23 #include "omaha/base/utils.h" | |
24 | |
25 namespace omaha { | |
26 | |
27 // Hold owner draw data | |
28 struct OwnerDrawData { | |
29 HFONT font; | |
30 CString text; | |
31 HICON icon; | |
32 | |
33 OwnerDrawData(HFONT f, const TCHAR* t, HICON i) { | |
34 font = f; | |
35 text = t; | |
36 icon = i; | |
37 } | |
38 }; | |
39 | |
40 | |
41 // Default constructor | |
42 PopupMenu::PopupMenu() | |
43 : wnd_(NULL) { | |
44 reset(menu_, ::CreatePopupMenu()); | |
45 ASSERT1(menu_); | |
46 } | |
47 | |
48 // Constructor | |
49 PopupMenu::PopupMenu(HINSTANCE inst, const TCHAR* name) | |
50 : wnd_(NULL) { | |
51 LoadFromResource(inst, name); | |
52 ASSERT1(menu_); | |
53 } | |
54 | |
55 // Destructor | |
56 PopupMenu::~PopupMenu() { | |
57 } | |
58 | |
59 // Load from resource | |
60 bool PopupMenu::LoadFromResource(HINSTANCE inst, const TCHAR* name) { | |
61 reset(menu_, GetSubMenu(reinterpret_cast<HMENU>(::LoadMenu(inst, name)), 0)); | |
62 return get(menu_) != NULL; | |
63 } | |
64 | |
65 // Append menu item | |
66 bool PopupMenu::AppendMenuItem(int menu_item_id, const TCHAR* text) { | |
67 return AppendMenuItem(menu_item_id, text, NULL); | |
68 } | |
69 | |
70 // Append menu item | |
71 bool PopupMenu::AppendMenuItem(int menu_item_id, | |
72 const TCHAR* text, | |
73 const MenuItemDrawStyle* style) { | |
74 int count = ::GetMenuItemCount(get(menu_)); | |
75 if (count == -1) | |
76 return false; | |
77 | |
78 return InsertMenuItem(menu_item_id, count, true, text, style); | |
79 } | |
80 | |
81 // Append separator | |
82 bool PopupMenu::AppendSeparator() { | |
83 return AppendMenuItem(-1, NULL, NULL); | |
84 } | |
85 | |
86 // Insert menu item | |
87 bool PopupMenu::InsertMenuItem(int menu_item_id, | |
88 int before_item, | |
89 bool by_pos, | |
90 const TCHAR* text) { | |
91 return InsertMenuItem(menu_item_id, before_item, by_pos, text, NULL); | |
92 } | |
93 | |
94 // Helper function that populates the MENUITEMINFO structure and sets | |
95 // accelerator keys for OWNERDRAW menu items | |
96 MENUITEMINFO PopupMenu::PrepareMenuItemInfo(int menu_item_id, const TCHAR* text, | |
97 const MenuItemDrawStyle* style) { | |
98 // Fill in the MENUITEMINFO structure | |
99 MENUITEMINFO menuitem_info; | |
100 SetZero(menuitem_info); | |
101 menuitem_info.cbSize = sizeof(MENUITEMINFO); | |
102 menuitem_info.wID = menu_item_id; | |
103 if (text == NULL) { | |
104 menuitem_info.fMask = MIIM_FTYPE | MIIM_ID; | |
105 menuitem_info.fType = MFT_SEPARATOR; | |
106 } else { | |
107 if (!style) { | |
108 menuitem_info.fMask = MIIM_STRING | MIIM_ID; | |
109 menuitem_info.fType = MFT_STRING; | |
110 menuitem_info.dwTypeData = const_cast<TCHAR*>(text); | |
111 } else { | |
112 // Handle bold font style | |
113 HFONT font = NULL; | |
114 if (style->is_bold) { | |
115 font = GetBoldFont(); | |
116 } | |
117 | |
118 // Remove '&' if it is there | |
119 CString text_str(text); | |
120 int pos = String_FindChar(text_str, _T('&')); | |
121 if (pos != -1) { | |
122 if (pos + 1 < text_str.GetLength()) { | |
123 accelerator_keys_.Add(Char_ToLower(text_str[pos + 1]), menu_item_id); | |
124 } | |
125 ReplaceCString(text_str, _T("&"), _T("")); | |
126 } | |
127 | |
128 // Set owner-draw related properties | |
129 OwnerDrawData* data = new OwnerDrawData(font, text_str, style->icon); | |
130 menuitem_info.fMask = MIIM_FTYPE | MIIM_DATA | MIIM_ID; | |
131 menuitem_info.fType = MFT_OWNERDRAW; | |
132 menuitem_info.dwItemData = reinterpret_cast<ULONG_PTR>(data); | |
133 } | |
134 } | |
135 | |
136 return menuitem_info; | |
137 } | |
138 | |
139 // Insert menu item | |
140 bool PopupMenu::InsertMenuItem(int menu_item_id, | |
141 int before_item, | |
142 bool by_pos, | |
143 const TCHAR* text, | |
144 const MenuItemDrawStyle* style) { | |
145 MENUITEMINFO menuitem_info = PrepareMenuItemInfo(menu_item_id, text, style); | |
146 if (!::InsertMenuItem(get(menu_), before_item, by_pos, &menuitem_info)) | |
147 return false; | |
148 | |
149 return Redraw(); | |
150 } | |
151 | |
152 // Insert separator | |
153 bool PopupMenu::InsertSeparator(int before_item, bool by_pos) { | |
154 return InsertMenuItem(-1, before_item, by_pos, NULL, NULL); | |
155 } | |
156 | |
157 // Modify a given menu item | |
158 bool PopupMenu::ModifyMenuItem(int menu_item, bool by_pos, const TCHAR* text, | |
159 const MenuItemDrawStyle* style) { | |
160 // Get OWNERDRAW data for later deletion | |
161 MENUITEMINFO menuitem_info; | |
162 SetZero(menuitem_info); | |
163 menuitem_info.cbSize = sizeof(MENUITEMINFO); | |
164 menuitem_info.fMask = MIIM_FTYPE | MIIM_DATA; | |
165 if (!::GetMenuItemInfo(get(menu_), menu_item, by_pos, &menuitem_info)) { | |
166 return false; | |
167 } | |
168 | |
169 OwnerDrawData* old_owner_data = NULL; | |
170 if ((menuitem_info.fType | MFT_OWNERDRAW) && menuitem_info.dwItemData) { | |
171 old_owner_data = | |
172 reinterpret_cast<OwnerDrawData *>(menuitem_info.dwItemData); | |
173 } | |
174 | |
175 // Remove old accelerator mapping | |
176 int menu_item_id = by_pos ? ::GetMenuItemID(get(menu_), menu_item) : | |
177 menu_item; | |
178 int key_pos = accelerator_keys_.FindVal(menu_item_id); | |
179 if (key_pos != -1) { | |
180 accelerator_keys_.RemoveAt(key_pos); | |
181 } | |
182 | |
183 // Set new menu item info | |
184 menuitem_info = PrepareMenuItemInfo(menu_item_id, text, style); | |
185 if (!::SetMenuItemInfo(get(menu_), menu_item, by_pos, &menuitem_info)) { | |
186 return false; | |
187 } | |
188 | |
189 // Delete old owner draw data | |
190 if (old_owner_data) { | |
191 delete old_owner_data; | |
192 } | |
193 | |
194 // Redraw | |
195 return Redraw(); | |
196 } | |
197 | |
198 // Remove a menu item | |
199 bool PopupMenu::RemoveMenuItem(int menu_item, bool by_pos) { | |
200 // Get OWNERDRAW data for later deletion | |
201 MENUITEMINFO menuitem_info; | |
202 SetZero(menuitem_info); | |
203 menuitem_info.cbSize = sizeof(MENUITEMINFO); | |
204 menuitem_info.fMask = MIIM_FTYPE | MIIM_DATA; | |
205 if (!::GetMenuItemInfo(get(menu_), menu_item, by_pos, &menuitem_info)) { | |
206 return false; | |
207 } | |
208 | |
209 OwnerDrawData* old_owner_data = NULL; | |
210 if ((menuitem_info.fType | MFT_OWNERDRAW) && menuitem_info.dwItemData) { | |
211 old_owner_data = | |
212 reinterpret_cast<OwnerDrawData *>(menuitem_info.dwItemData); | |
213 } | |
214 | |
215 // Remove the menu item | |
216 if (!::RemoveMenu(get(menu_), menu_item, by_pos ? MF_BYPOSITION : | |
217 MF_BYCOMMAND)) { | |
218 return false; | |
219 } | |
220 | |
221 // Remove old accelerator mapping | |
222 int menu_item_id = by_pos ? ::GetMenuItemID(get(menu_), menu_item) : | |
223 menu_item; | |
224 int key_pos = accelerator_keys_.FindVal(menu_item_id); | |
225 if (key_pos != -1) { | |
226 accelerator_keys_.RemoveAt(key_pos); | |
227 } | |
228 | |
229 // Delete old owner draw data | |
230 if (old_owner_data) { | |
231 delete old_owner_data; | |
232 } | |
233 | |
234 // Redraw | |
235 return Redraw(); | |
236 } | |
237 | |
238 // Enable menu item | |
239 bool PopupMenu::EnableMenuItem(int menu_item, bool by_pos, bool enabled) { | |
240 if (::EnableMenuItem(get(menu_), menu_item, | |
241 (by_pos ? MF_BYPOSITION : MF_BYCOMMAND) | | |
242 (enabled ? MF_ENABLED : MF_GRAYED)) == -1) | |
243 return false; | |
244 | |
245 return Redraw(); | |
246 } | |
247 | |
248 // Get menu state | |
249 bool PopupMenu::GetMenuState(int menu_item, bool by_pos, int* menu_state) { | |
250 int state = ::GetMenuState(get(menu_), | |
251 menu_item, by_pos ? MF_BYPOSITION : MF_BYCOMMAND); | |
252 if (menu_state) | |
253 *menu_state = state; | |
254 return state != -1; | |
255 } | |
256 | |
257 // Exists a menu item | |
258 bool PopupMenu::ExistsMenuItem(int menu_item_id) { | |
259 return GetMenuState(menu_item_id, false, NULL); | |
260 } | |
261 | |
262 // Redraw menu | |
263 bool PopupMenu::Redraw() { | |
264 if (!wnd_) | |
265 return true; | |
266 | |
267 return ::DrawMenuBar(wnd_) == TRUE; | |
268 } | |
269 | |
270 // Track menu | |
271 bool PopupMenu::Track() { | |
272 ASSERT1(wnd_); | |
273 | |
274 // If we don't set it to be foreground, it will not stop tracking even | |
275 // if we click outside of menu. | |
276 ::SetForegroundWindow(wnd_); | |
277 | |
278 POINT point = {0, 0}; | |
279 VERIFY(::GetCursorPos(&point), (_T(""))); | |
280 | |
281 uint32 kFlags = TPM_LEFTALIGN | | |
282 TPM_RETURNCMD | | |
283 TPM_NONOTIFY | | |
284 TPM_LEFTBUTTON | | |
285 TPM_VERTICAL; | |
286 int command = ::TrackPopupMenuEx(get(menu_), | |
287 kFlags, | |
288 point.x, point.y, wnd_, NULL); | |
289 | |
290 if (command != 0) | |
291 ::SendMessage(wnd_, WM_COMMAND, command, 0); | |
292 | |
293 return true; | |
294 } | |
295 | |
296 // Handle WM_MEASUREITEM message | |
297 bool PopupMenu::OnMeasureItem(MEASUREITEMSTRUCT* mi) { | |
298 ASSERT1(wnd_); | |
299 | |
300 // Get owner draw data | |
301 ASSERT1(mi->itemData); | |
302 OwnerDrawData* data = reinterpret_cast<OwnerDrawData*>(mi->itemData); | |
303 | |
304 // Get the DC | |
305 scoped_hdc dc; | |
306 reset(dc, ::GetDC(wnd_)); | |
307 | |
308 // Select the font | |
309 HFONT old_font = reinterpret_cast<HFONT>(::SelectObject(get(dc), data->font)); | |
310 if (!old_font) | |
311 return false; | |
312 | |
313 // compute the size of the text | |
314 SIZE size = {0, 0}; | |
315 bool success = ::GetTextExtentPoint32(get(dc), | |
316 data->text.GetString(), | |
317 data->text.GetLength(), | |
318 &size) != 0; | |
319 if (success) { | |
320 mi->itemWidth = size.cx; | |
321 mi->itemHeight = size.cy; | |
322 } | |
323 | |
324 // deselect the title font | |
325 ::SelectObject(get(dc), old_font); | |
326 | |
327 return success; | |
328 } | |
329 | |
330 // Handle WM_MDRAWITEM message | |
331 bool PopupMenu::OnDrawItem(DRAWITEMSTRUCT* di) { | |
332 ASSERT1(di); | |
333 | |
334 // Get owner draw data | |
335 ASSERT1(di->itemData); | |
336 OwnerDrawData* data = reinterpret_cast<OwnerDrawData*>(di->itemData); | |
337 | |
338 // Select the font | |
339 HFONT prev_font = NULL; | |
340 if (data->font) { | |
341 prev_font = reinterpret_cast<HFONT>(::SelectObject(di->hDC, data->font)); | |
342 if (!prev_font) { | |
343 return false; | |
344 } | |
345 } | |
346 | |
347 // Draw the text per the menuitem state | |
348 int fg_color_idx = | |
349 (di->itemState & ODS_DISABLED) ? | |
350 COLOR_GRAYTEXT : | |
351 ((di->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHTTEXT : COLOR_MENUTEXT); | |
352 | |
353 int bg_color_idx = | |
354 (di->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT : COLOR_MENU; | |
355 | |
356 bool success = DrawText(data->text, di, fg_color_idx, bg_color_idx); | |
357 | |
358 // Restore the original font | |
359 if (prev_font) { | |
360 ::SelectObject(di->hDC, prev_font); | |
361 } | |
362 | |
363 // Compute the width and height | |
364 int height = di->rcItem.bottom - di->rcItem.top + 1; | |
365 int width = static_cast<int>(::GetSystemMetrics(SM_CXMENUCHECK) * | |
366 (static_cast<double>(height) / ::GetSystemMetrics(SM_CYMENUCHECK))); | |
367 | |
368 // Draw the icon | |
369 // TODO(omaha): Draw a grayed icon when the menuitem is disabled | |
370 if (success && data->icon) { | |
371 success = ::DrawIconEx(di->hDC, | |
372 di->rcItem.left, | |
373 di->rcItem.top, | |
374 data->icon, | |
375 width, | |
376 height, | |
377 0, | |
378 NULL, | |
379 DI_NORMAL) != 0; | |
380 } | |
381 | |
382 return success; | |
383 } | |
384 | |
385 // Draw the text | |
386 bool PopupMenu::DrawText(const CString& text, | |
387 DRAWITEMSTRUCT* di, | |
388 int fg_color_idx, | |
389 int bg_color_idx) { | |
390 // Set the appropriate foreground and background colors | |
391 COLORREF prev_fg_color = 0, prev_bg_color = 0; | |
392 prev_fg_color = ::SetTextColor(di->hDC, ::GetSysColor(fg_color_idx)); | |
393 if (prev_fg_color == CLR_INVALID) { | |
394 return false; | |
395 } | |
396 prev_bg_color = ::SetBkColor(di->hDC, ::GetSysColor(bg_color_idx)); | |
397 if (prev_bg_color == CLR_INVALID) { | |
398 return false; | |
399 } | |
400 | |
401 // Draw the text | |
402 bool success = ::ExtTextOut( | |
403 di->hDC, | |
404 di->rcItem.left + ::GetSystemMetrics(SM_CXMENUCHECK) + 4, | |
405 di->rcItem.top, | |
406 ETO_OPAQUE, | |
407 &di->rcItem, | |
408 text.GetString(), | |
409 text.GetLength(), | |
410 NULL) == TRUE; | |
411 | |
412 // Restore the original colors | |
413 ::SetTextColor(di->hDC, prev_fg_color); | |
414 ::SetBkColor(di->hDC, prev_bg_color); | |
415 | |
416 return success; | |
417 } | |
418 | |
419 // Handle WM_MENUCHAR message | |
420 int PopupMenu::OnMenuChar(TCHAR key) { | |
421 int pos = accelerator_keys_.FindKey(Char_ToLower(key)); | |
422 if (pos != -1) | |
423 return GetMenuPosFromID(accelerator_keys_.GetValueAt(pos)); | |
424 else | |
425 return -1; | |
426 } | |
427 | |
428 HFONT PopupMenu::GetBoldFont() { | |
429 if (!bold_font_) { | |
430 NONCLIENTMETRICS ncm; | |
431 SetZero(ncm); | |
432 ncm.cbSize = sizeof(NONCLIENTMETRICS); | |
433 if (::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0)) { | |
434 ncm.lfMenuFont.lfWeight = FW_BOLD; | |
435 reset(bold_font_, ::CreateFontIndirect(&ncm.lfMenuFont)); | |
436 } else { | |
437 UTIL_LOG(LEVEL_ERROR, (_T("[PopupMenu::GetBoldFont]") | |
438 _T("[failed to get system menu font][0x%x]"), | |
439 HRESULTFromLastError())); | |
440 } | |
441 } | |
442 | |
443 ASSERT1(bold_font_); | |
444 | |
445 return get(bold_font_); | |
446 } | |
447 | |
448 // Get menu pos from ID | |
449 int PopupMenu::GetMenuPosFromID(int id) { | |
450 ASSERT1(id >= 0); | |
451 int count = ::GetMenuItemCount(get(menu_)); | |
452 if (count > 0) { | |
453 for (int pos = 0; pos < count; ++pos) { | |
454 if (::GetMenuItemID(get(menu_), pos) == static_cast<UINT>(id)) { | |
455 return pos; | |
456 } | |
457 } | |
458 } | |
459 | |
460 return -1; | |
461 } | |
462 | |
463 } // namespace omaha | |
464 | |
OLD | NEW |