blob: 7b9c00ac8f464e996c57fafb3626ed9272980d33 [file] [log] [blame]
// Copyright 2005-2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
#include "omaha/common/popup_menu.h"
#include <windows.h>
#include "omaha/common/debug.h"
#include "omaha/common/error.h"
#include "omaha/common/logging.h"
#include "omaha/common/string.h"
#include "omaha/common/utils.h"
namespace omaha {
// Hold owner draw data
struct OwnerDrawData {
HFONT font;
CString text;
HICON icon;
OwnerDrawData(HFONT f, const TCHAR* t, HICON i) {
font = f;
text = t;
icon = i;
}
};
// Default constructor
PopupMenu::PopupMenu()
: wnd_(NULL) {
reset(menu_, ::CreatePopupMenu());
ASSERT1(menu_);
}
// Constructor
PopupMenu::PopupMenu(HINSTANCE inst, const TCHAR* name)
: wnd_(NULL) {
LoadFromResource(inst, name);
ASSERT1(menu_);
}
// Destructor
PopupMenu::~PopupMenu() {
}
// Load from resource
bool PopupMenu::LoadFromResource(HINSTANCE inst, const TCHAR* name) {
reset(menu_, GetSubMenu(reinterpret_cast<HMENU>(::LoadMenu(inst, name)), 0));
return get(menu_) != NULL;
}
// Append menu item
bool PopupMenu::AppendMenuItem(int menu_item_id, const TCHAR* text) {
return AppendMenuItem(menu_item_id, text, NULL);
}
// Append menu item
bool PopupMenu::AppendMenuItem(int menu_item_id,
const TCHAR* text,
const MenuItemDrawStyle* style) {
int count = ::GetMenuItemCount(get(menu_));
if (count == -1)
return false;
return InsertMenuItem(menu_item_id, count, true, text, style);
}
// Append separator
bool PopupMenu::AppendSeparator() {
return AppendMenuItem(-1, NULL, NULL);
}
// Insert menu item
bool PopupMenu::InsertMenuItem(int menu_item_id,
int before_item,
bool by_pos,
const TCHAR* text) {
return InsertMenuItem(menu_item_id, before_item, by_pos, text, NULL);
}
// Helper function that populates the MENUITEMINFO structure and sets
// accelerator keys for OWNERDRAW menu items
MENUITEMINFO PopupMenu::PrepareMenuItemInfo(int menu_item_id, const TCHAR* text,
const MenuItemDrawStyle* style) {
// Fill in the MENUITEMINFO structure
MENUITEMINFO menuitem_info;
SetZero(menuitem_info);
menuitem_info.cbSize = sizeof(MENUITEMINFO);
menuitem_info.wID = menu_item_id;
if (text == NULL) {
menuitem_info.fMask = MIIM_FTYPE | MIIM_ID;
menuitem_info.fType = MFT_SEPARATOR;
} else {
if (!style) {
menuitem_info.fMask = MIIM_STRING | MIIM_ID;
menuitem_info.fType = MFT_STRING;
menuitem_info.dwTypeData = const_cast<TCHAR*>(text);
} else {
// Handle bold font style
HFONT font = NULL;
if (style->is_bold) {
font = GetBoldFont();
}
// Remove '&' if it is there
CString text_str(text);
int pos = String_FindChar(text_str, _T('&'));
if (pos != -1) {
if (pos + 1 < text_str.GetLength()) {
accelerator_keys_.Add(Char_ToLower(text_str[pos + 1]), menu_item_id);
}
ReplaceCString(text_str, _T("&"), _T(""));
}
// Set owner-draw related properties
OwnerDrawData* data = new OwnerDrawData(font, text_str, style->icon);
menuitem_info.fMask = MIIM_FTYPE | MIIM_DATA | MIIM_ID;
menuitem_info.fType = MFT_OWNERDRAW;
menuitem_info.dwItemData = reinterpret_cast<ULONG_PTR>(data);
}
}
return menuitem_info;
}
// Insert menu item
bool PopupMenu::InsertMenuItem(int menu_item_id,
int before_item,
bool by_pos,
const TCHAR* text,
const MenuItemDrawStyle* style) {
MENUITEMINFO menuitem_info = PrepareMenuItemInfo(menu_item_id, text, style);
if (!::InsertMenuItem(get(menu_), before_item, by_pos, &menuitem_info))
return false;
return Redraw();
}
// Insert separator
bool PopupMenu::InsertSeparator(int before_item, bool by_pos) {
return InsertMenuItem(-1, before_item, by_pos, NULL, NULL);
}
// Modify a given menu item
bool PopupMenu::ModifyMenuItem(int menu_item, bool by_pos, const TCHAR* text,
const MenuItemDrawStyle* style) {
// Get OWNERDRAW data for later deletion
MENUITEMINFO menuitem_info;
SetZero(menuitem_info);
menuitem_info.cbSize = sizeof(MENUITEMINFO);
menuitem_info.fMask = MIIM_FTYPE | MIIM_DATA;
if (!::GetMenuItemInfo(get(menu_), menu_item, by_pos, &menuitem_info)) {
return false;
}
OwnerDrawData* old_owner_data = NULL;
if ((menuitem_info.fType | MFT_OWNERDRAW) && menuitem_info.dwItemData) {
old_owner_data =
reinterpret_cast<OwnerDrawData *>(menuitem_info.dwItemData);
}
// Remove old accelerator mapping
int menu_item_id = by_pos ? ::GetMenuItemID(get(menu_), menu_item) :
menu_item;
int key_pos = accelerator_keys_.FindVal(menu_item_id);
if (key_pos != -1) {
accelerator_keys_.RemoveAt(key_pos);
}
// Set new menu item info
menuitem_info = PrepareMenuItemInfo(menu_item_id, text, style);
if (!::SetMenuItemInfo(get(menu_), menu_item, by_pos, &menuitem_info)) {
return false;
}
// Delete old owner draw data
if (old_owner_data) {
delete old_owner_data;
}
// Redraw
return Redraw();
}
// Remove a menu item
bool PopupMenu::RemoveMenuItem(int menu_item, bool by_pos) {
// Get OWNERDRAW data for later deletion
MENUITEMINFO menuitem_info;
SetZero(menuitem_info);
menuitem_info.cbSize = sizeof(MENUITEMINFO);
menuitem_info.fMask = MIIM_FTYPE | MIIM_DATA;
if (!::GetMenuItemInfo(get(menu_), menu_item, by_pos, &menuitem_info)) {
return false;
}
OwnerDrawData* old_owner_data = NULL;
if ((menuitem_info.fType | MFT_OWNERDRAW) && menuitem_info.dwItemData) {
old_owner_data =
reinterpret_cast<OwnerDrawData *>(menuitem_info.dwItemData);
}
// Remove the menu item
if (!::RemoveMenu(get(menu_), menu_item, by_pos ? MF_BYPOSITION :
MF_BYCOMMAND)) {
return false;
}
// Remove old accelerator mapping
int menu_item_id = by_pos ? ::GetMenuItemID(get(menu_), menu_item) :
menu_item;
int key_pos = accelerator_keys_.FindVal(menu_item_id);
if (key_pos != -1) {
accelerator_keys_.RemoveAt(key_pos);
}
// Delete old owner draw data
if (old_owner_data) {
delete old_owner_data;
}
// Redraw
return Redraw();
}
// Enable menu item
bool PopupMenu::EnableMenuItem(int menu_item, bool by_pos, bool enabled) {
if (::EnableMenuItem(get(menu_), menu_item,
(by_pos ? MF_BYPOSITION : MF_BYCOMMAND) |
(enabled ? MF_ENABLED : MF_GRAYED)) == -1)
return false;
return Redraw();
}
// Get menu state
bool PopupMenu::GetMenuState(int menu_item, bool by_pos, int* menu_state) {
int state = ::GetMenuState(get(menu_),
menu_item, by_pos ? MF_BYPOSITION : MF_BYCOMMAND);
if (menu_state)
*menu_state = state;
return state != -1;
}
// Exists a menu item
bool PopupMenu::ExistsMenuItem(int menu_item_id) {
return GetMenuState(menu_item_id, false, NULL);
}
// Redraw menu
bool PopupMenu::Redraw() {
if (!wnd_)
return true;
return ::DrawMenuBar(wnd_) == TRUE;
}
// Track menu
bool PopupMenu::Track() {
ASSERT1(wnd_);
// If we don't set it to be foreground, it will not stop tracking even
// if we click outside of menu.
::SetForegroundWindow(wnd_);
POINT point = {0, 0};
VERIFY(::GetCursorPos(&point), (_T("")));
uint32 kFlags = TPM_LEFTALIGN |
TPM_RETURNCMD |
TPM_NONOTIFY |
TPM_LEFTBUTTON |
TPM_VERTICAL;
int command = ::TrackPopupMenuEx(get(menu_),
kFlags,
point.x, point.y, wnd_, NULL);
if (command != 0)
::SendMessage(wnd_, WM_COMMAND, command, 0);
return true;
}
// Handle WM_MEASUREITEM message
bool PopupMenu::OnMeasureItem(MEASUREITEMSTRUCT* mi) {
ASSERT1(wnd_);
// Get owner draw data
ASSERT1(mi->itemData);
OwnerDrawData* data = reinterpret_cast<OwnerDrawData*>(mi->itemData);
// Get the DC
scoped_hdc dc;
reset(dc, ::GetDC(wnd_));
// Select the font
HFONT old_font = reinterpret_cast<HFONT>(::SelectObject(get(dc), data->font));
if (!old_font)
return false;
// compute the size of the text
SIZE size = {0, 0};
bool success = ::GetTextExtentPoint32(get(dc),
data->text.GetString(),
data->text.GetLength(),
&size) != 0;
if (success) {
mi->itemWidth = size.cx;
mi->itemHeight = size.cy;
}
// deselect the title font
::SelectObject(get(dc), old_font);
return success;
}
// Handle WM_MDRAWITEM message
bool PopupMenu::OnDrawItem(DRAWITEMSTRUCT* di) {
ASSERT1(di);
// Get owner draw data
ASSERT1(di->itemData);
OwnerDrawData* data = reinterpret_cast<OwnerDrawData*>(di->itemData);
// Select the font
HFONT prev_font = NULL;
if (data->font) {
prev_font = reinterpret_cast<HFONT>(::SelectObject(di->hDC, data->font));
if (!prev_font) {
return false;
}
}
// Draw the text per the menuitem state
int fg_color_idx =
(di->itemState & ODS_DISABLED) ?
COLOR_GRAYTEXT :
((di->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHTTEXT : COLOR_MENUTEXT);
int bg_color_idx =
(di->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT : COLOR_MENU;
bool success = DrawText(data->text, di, fg_color_idx, bg_color_idx);
// Restore the original font
if (prev_font) {
::SelectObject(di->hDC, prev_font);
}
// Compute the width and height
int height = di->rcItem.bottom - di->rcItem.top + 1;
int width = static_cast<int>(::GetSystemMetrics(SM_CXMENUCHECK) *
(static_cast<double>(height) / ::GetSystemMetrics(SM_CYMENUCHECK)));
// Draw the icon
// TODO(omaha): Draw a grayed icon when the menuitem is disabled
if (success && data->icon) {
success = ::DrawIconEx(di->hDC,
di->rcItem.left,
di->rcItem.top,
data->icon,
width,
height,
0,
NULL,
DI_NORMAL) != 0;
}
return success;
}
// Draw the text
bool PopupMenu::DrawText(const CString& text,
DRAWITEMSTRUCT* di,
int fg_color_idx,
int bg_color_idx) {
// Set the appropriate foreground and background colors
COLORREF prev_fg_color = 0, prev_bg_color = 0;
prev_fg_color = ::SetTextColor(di->hDC, ::GetSysColor(fg_color_idx));
if (prev_fg_color == CLR_INVALID) {
return false;
}
prev_bg_color = ::SetBkColor(di->hDC, ::GetSysColor(bg_color_idx));
if (prev_bg_color == CLR_INVALID) {
return false;
}
// Draw the text
bool success = ::ExtTextOut(
di->hDC,
di->rcItem.left + ::GetSystemMetrics(SM_CXMENUCHECK) + 4,
di->rcItem.top,
ETO_OPAQUE,
&di->rcItem,
text.GetString(),
text.GetLength(),
NULL) == TRUE;
// Restore the original colors
::SetTextColor(di->hDC, prev_fg_color);
::SetBkColor(di->hDC, prev_bg_color);
return success;
}
// Handle WM_MENUCHAR message
int PopupMenu::OnMenuChar(TCHAR key) {
int pos = accelerator_keys_.FindKey(Char_ToLower(key));
if (pos != -1)
return GetMenuPosFromID(accelerator_keys_.GetValueAt(pos));
else
return -1;
}
HFONT PopupMenu::GetBoldFont() {
if (!bold_font_) {
NONCLIENTMETRICS ncm;
SetZero(ncm);
ncm.cbSize = sizeof(NONCLIENTMETRICS);
if (::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0)) {
ncm.lfMenuFont.lfWeight = FW_BOLD;
reset(bold_font_, ::CreateFontIndirect(&ncm.lfMenuFont));
} else {
UTIL_LOG(LEVEL_ERROR, (_T("[PopupMenu::GetBoldFont]")
_T("[failed to get system menu font][0x%x]"),
HRESULTFromLastError()));
}
}
ASSERT1(bold_font_);
return get(bold_font_);
}
// Get menu pos from ID
int PopupMenu::GetMenuPosFromID(int id) {
ASSERT1(id >= 0);
int count = ::GetMenuItemCount(get(menu_));
if (count > 0) {
for (int pos = 0; pos < count; ++pos) {
if (::GetMenuItemID(get(menu_), pos) == static_cast<UINT>(id)) {
return pos;
}
}
}
return -1;
}
} // namespace omaha