diff options
author | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-16 13:31:18 +0000 |
---|---|---|
committer | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-16 13:31:18 +0000 |
commit | 990e62268f8a7074995ea00895da064312e49535 (patch) | |
tree | 6447bc15cd3e2f728b7351b27571a3c6da35ae41 /ui/native_theme/native_theme_win.cc | |
parent | 8d0f9016803d12e160b588cca2a7fc4612323aad (diff) | |
download | chromium_src-990e62268f8a7074995ea00895da064312e49535.zip chromium_src-990e62268f8a7074995ea00895da064312e49535.tar.gz chromium_src-990e62268f8a7074995ea00895da064312e49535.tar.bz2 |
ui: Make native_theme its own library.
BUG=103304
R=sky@chromium.org
Review URL: https://chromiumcodereview.appspot.com/11275322
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@168194 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/native_theme/native_theme_win.cc')
-rw-r--r-- | ui/native_theme/native_theme_win.cc | 1944 |
1 files changed, 1944 insertions, 0 deletions
diff --git a/ui/native_theme/native_theme_win.cc b/ui/native_theme/native_theme_win.cc new file mode 100644 index 0000000..d83f396 --- /dev/null +++ b/ui/native_theme/native_theme_win.cc @@ -0,0 +1,1944 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/native_theme/native_theme_win.h" + +#include <windows.h> +#include <uxtheme.h> +#include <vsstyle.h> +#include <vssym32.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_handle.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/scoped_gdi_object.h" +#include "base/win/scoped_hdc.h" +#include "base/win/scoped_select_object.h" +#include "base/win/windows_version.h" +#include "skia/ext/bitmap_platform_device.h" +#include "skia/ext/platform_canvas.h" +#include "skia/ext/skia_utils_win.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "third_party/skia/include/core/SkShader.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/gdi_util.h" +#include "ui/gfx/rect.h" + +namespace { + +// TODO: Obtain the correct colors using GetSysColor. +// Theme colors returned by GetSystemColor(). +const SkColor kInvalidColorIdColor = SkColorSetRGB(255, 0, 128); +// Dialogs: +const SkColor kDialogBackgroundColor = SkColorSetRGB(200, 200, 200); +// FocusableBorder: +const SkColor kFocusedBorderColor = SkColorSetRGB(0x4D, 0x90, 0xFE); +const SkColor kUnfocusedBorderColor = SkColorSetRGB(0xD9, 0xD9, 0xD9); +// TextButton: +const SkColor kTextButtonBackgroundColor = SkColorSetRGB(0xde, 0xde, 0xde); +const SkColor kTextButtonEnabledColor = SkColorSetRGB(6, 45, 117); +const SkColor kTextButtonDisabledColor = SkColorSetRGB(161, 161, 146); +const SkColor kTextButtonHighlightColor = SkColorSetARGB(200, 255, 255, 255); +const SkColor kTextButtonHoverColor = kTextButtonEnabledColor; +// MenuItem: +const SkColor kEnabledMenuItemForegroundColor = kTextButtonEnabledColor; +const SkColor kDisabledMenuItemForegroundColor = kTextButtonDisabledColor; +const SkColor kFocusedMenuItemBackgroundColor = SkColorSetRGB(246, 249, 253); +const SkColor kMenuSeparatorColor = SkColorSetARGB(50, 0, 0, 0); +// Label: +const SkColor kLabelEnabledColor = color_utils::GetSysSkColor(COLOR_WINDOWTEXT); +const SkColor kLabelDisabledColor = color_utils::GetSysSkColor(COLOR_GRAYTEXT); +const SkColor kLabelBackgroundColor = color_utils::GetSysSkColor(COLOR_WINDOW); +// Textfield: +const SkColor kTextfieldDefaultColor = SK_ColorBLACK; +const SkColor kTextfieldDefaultBackground = SK_ColorWHITE; +const SkColor kTextfieldSelectionColor = SK_ColorWHITE; +const SkColor kTextfieldSelectionBackgroundFocused = + SkColorSetRGB(0x1D, 0x90, 0xFF); +const SkColor kTextfieldSelectionBackgroundUnfocused = SK_ColorLTGRAY; + +SkColor WinColorToSkColor(COLORREF color) { + return SkColorSetRGB(GetRValue(color), GetGValue(color), GetBValue(color)); +} + +void SetCheckerboardShader(SkPaint* paint, const RECT& align_rect) { + // Create a 2x2 checkerboard pattern using the 3D face and highlight colors. + SkColor face = skia::COLORREFToSkColor(GetSysColor(COLOR_3DFACE)); + SkColor highlight = skia::COLORREFToSkColor(GetSysColor(COLOR_3DHILIGHT)); + SkColor buffer[] = { face, highlight, highlight, face }; + // Confusing bit: we first create a temporary bitmap with our desired pattern, + // then copy it to another bitmap. The temporary bitmap doesn't take + // ownership of the pixel data, and so will point to garbage when this + // function returns. The copy will copy the pixel data into a place owned by + // the bitmap, which is in turn owned by the shader, etc., so it will live + // until we're done using it. + SkBitmap temp_bitmap; + temp_bitmap.setConfig(SkBitmap::kARGB_8888_Config, 2, 2); + temp_bitmap.setPixels(buffer); + SkBitmap bitmap; + temp_bitmap.copyTo(&bitmap, temp_bitmap.config()); + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + + // Align the pattern with the upper corner of |align_rect|. + SkMatrix matrix; + matrix.setTranslate(SkIntToScalar(align_rect.left), + SkIntToScalar(align_rect.top)); + shader->setLocalMatrix(matrix); + SkSafeUnref(paint->setShader(shader)); +} + +// <-a-> +// [ ***** ] +// ____ | | +// <-a-> <------b-----> +// a: object_width +// b: frame_width +// *: animating object +// +// - the animation goes from "[" to "]" repeatedly. +// - the animation offset is at first "|" +// +int ComputeAnimationProgress(int frame_width, + int object_width, + int pixels_per_second, + double animated_seconds) { + int animation_width = frame_width + object_width; + double interval = static_cast<double>(animation_width) / pixels_per_second; + double ratio = fmod(animated_seconds, interval) / interval; + return static_cast<int>(animation_width * ratio) - object_width; +} + +RECT InsetRect(const RECT* rect, int size) { + gfx::Rect result(*rect); + result.Inset(size, size); + return result.ToRECT(); +} + +} // namespace + +namespace ui { + +bool NativeThemeWin::IsThemingActive() const { + if (is_theme_active_) + return !!is_theme_active_(); + return false; +} + +HRESULT NativeThemeWin::GetThemeColor(ThemeName theme, + int part_id, + int state_id, + int prop_id, + SkColor* color) const { + HANDLE handle = GetThemeHandle(theme); + if (handle && get_theme_color_) { + COLORREF color_ref; + if (get_theme_color_(handle, part_id, state_id, prop_id, &color_ref) == + S_OK) { + *color = skia::COLORREFToSkColor(color_ref); + return S_OK; + } + } + return E_NOTIMPL; +} + +SkColor NativeThemeWin::GetThemeColorWithDefault(ThemeName theme, + int part_id, + int state_id, + int prop_id, + int default_sys_color) const { + SkColor color; + if (GetThemeColor(theme, part_id, state_id, prop_id, &color) != S_OK) + color = skia::COLORREFToSkColor(GetSysColor(default_sys_color)); + return color; +} + +gfx::Size NativeThemeWin::GetThemeBorderSize(ThemeName theme) const { + // For simplicity use the wildcard state==0, part==0, since it works + // for the cases we currently depend on. + int border; + if (GetThemeInt(theme, 0, 0, TMT_BORDERSIZE, &border) == S_OK) + return gfx::Size(border, border); + else + return gfx::Size(GetSystemMetrics(SM_CXEDGE), GetSystemMetrics(SM_CYEDGE)); +} + +void NativeThemeWin::DisableTheming() const { + if (!set_theme_properties_) + return; + set_theme_properties_(0); +} + +void NativeThemeWin::CloseHandles() const { + if (!close_theme_) + return; + + for (int i = 0; i < LAST; ++i) { + if (theme_handles_[i]) { + close_theme_(theme_handles_[i]); + theme_handles_[i] = NULL; + } + } +} + +bool NativeThemeWin::IsClassicTheme(ThemeName name) const { + if (!theme_dll_) + return true; + + return !GetThemeHandle(name); +} + +// TODO(sky): seems like we should default to NativeThemeWin, but that currently +// breaks a couple of tests (FocusTraversalTest.NormalTraversal in +// views_unittests). +#if !defined(USE_AURA) +// static +NativeTheme* NativeTheme::instance() { + return NativeThemeWin::instance(); +} +#endif + +// static +NativeThemeWin* NativeThemeWin::instance() { + CR_DEFINE_STATIC_LOCAL(NativeThemeWin, s_native_theme, ()); + return &s_native_theme; +} + +gfx::Size NativeThemeWin::GetPartSize(Part part, + State state, + const ExtraParams& extra) const { + SIZE size; + int part_id = GetWindowsPart(part, state, extra); + int state_id = GetWindowsState(part, state, extra); + + HDC hdc = GetDC(NULL); + HRESULT hr = GetThemePartSize(GetThemeName(part), hdc, part_id, state_id, + NULL, TS_TRUE, &size); + ReleaseDC(NULL, hdc); + + if (FAILED(hr)) { + // TODO(rogerta): For now, we need to support radio buttons and checkboxes + // when theming is not enabled. Support for other parts can be added + // if/when needed. + switch (part) { + case kCheckbox: + case kRadio: + // TODO(rogerta): I was not able to find any API to get the default + // size of these controls, so determined these values empirically. + size.cx = 13; + size.cy = 13; + break; + default: + size.cx = 0; + size.cy = 0; + break; + } + } + + return gfx::Size(size.cx, size.cy); +} + +void NativeThemeWin::Paint(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra) const { + bool needs_paint_indirect = false; + if (!skia::SupportsPlatformPaint(canvas)) { + // This block will only get hit with --enable-accelerated-drawing flag. + needs_paint_indirect = true; + } else { + // Scrollbars on Windows XP and the Windows Classic theme have particularly + // problematic alpha values, so always draw them indirectly. + switch (part) { + case kScrollbarDownArrow: + case kScrollbarUpArrow: + case kScrollbarLeftArrow: + case kScrollbarRightArrow: + case kScrollbarHorizontalThumb: + case kScrollbarVerticalThumb: + case kScrollbarHorizontalGripper: + case kScrollbarVerticalGripper: + if (!GetThemeHandle(SCROLLBAR)) + needs_paint_indirect = true; + break; + default: + break; + } + } + + if (needs_paint_indirect) + PaintIndirect(canvas, part, state, rect, extra); + else + PaintDirect(canvas, part, state, rect, extra); +} + +NativeThemeWin::NativeThemeWin() + : theme_dll_(LoadLibrary(L"uxtheme.dll")), + draw_theme_(NULL), + draw_theme_ex_(NULL), + get_theme_color_(NULL), + get_theme_content_rect_(NULL), + get_theme_part_size_(NULL), + open_theme_(NULL), + close_theme_(NULL), + set_theme_properties_(NULL), + is_theme_active_(NULL), + get_theme_int_(NULL) { + if (theme_dll_) { + draw_theme_ = reinterpret_cast<DrawThemeBackgroundPtr>( + GetProcAddress(theme_dll_, "DrawThemeBackground")); + draw_theme_ex_ = reinterpret_cast<DrawThemeBackgroundExPtr>( + GetProcAddress(theme_dll_, "DrawThemeBackgroundEx")); + get_theme_color_ = reinterpret_cast<GetThemeColorPtr>( + GetProcAddress(theme_dll_, "GetThemeColor")); + get_theme_content_rect_ = reinterpret_cast<GetThemeContentRectPtr>( + GetProcAddress(theme_dll_, "GetThemeBackgroundContentRect")); + get_theme_part_size_ = reinterpret_cast<GetThemePartSizePtr>( + GetProcAddress(theme_dll_, "GetThemePartSize")); + open_theme_ = reinterpret_cast<OpenThemeDataPtr>( + GetProcAddress(theme_dll_, "OpenThemeData")); + close_theme_ = reinterpret_cast<CloseThemeDataPtr>( + GetProcAddress(theme_dll_, "CloseThemeData")); + set_theme_properties_ = reinterpret_cast<SetThemeAppPropertiesPtr>( + GetProcAddress(theme_dll_, "SetThemeAppProperties")); + is_theme_active_ = reinterpret_cast<IsThemeActivePtr>( + GetProcAddress(theme_dll_, "IsThemeActive")); + get_theme_int_ = reinterpret_cast<GetThemeIntPtr>( + GetProcAddress(theme_dll_, "GetThemeInt")); + } + memset(theme_handles_, 0, sizeof(theme_handles_)); +} + +NativeThemeWin::~NativeThemeWin() { + if (theme_dll_) { + // todo (cpu): fix this soon. Making a call to CloseHandles() here breaks + // certain tests and the reliability bots. + // CloseHandles(); + FreeLibrary(theme_dll_); + } +} + +void NativeThemeWin::PaintDirect(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra) const { + skia::ScopedPlatformPaint scoped_platform_paint(canvas); + HDC hdc = scoped_platform_paint.GetPlatformSurface(); + + switch (part) { + case kCheckbox: + PaintCheckbox(hdc, part, state, rect, extra.button); + break; + case kRadio: + PaintRadioButton(hdc, part, state, rect, extra.button); + break; + case kPushButton: + PaintPushButton(hdc, part, state, rect, extra.button); + break; + case kMenuPopupArrow: + PaintMenuArrow(hdc, state, rect, extra.menu_arrow); + break; + case kMenuPopupGutter: + PaintMenuGutter(hdc, rect); + break; + case kMenuPopupSeparator: + PaintMenuSeparator(hdc, rect, extra.menu_separator); + break; + case kMenuPopupBackground: + PaintMenuBackground(hdc, rect); + break; + case kMenuCheck: + PaintMenuCheck(hdc, state, rect, extra.menu_check); + break; + case kMenuCheckBackground: + PaintMenuCheckBackground(hdc, state, rect); + break; + case kMenuItemBackground: + PaintMenuItemBackground(hdc, state, rect, extra.menu_item); + break; + case kMenuList: + PaintMenuList(hdc, state, rect, extra.menu_list); + break; + case kScrollbarDownArrow: + case kScrollbarUpArrow: + case kScrollbarLeftArrow: + case kScrollbarRightArrow: + PaintScrollbarArrow(hdc, part, state, rect, extra.scrollbar_arrow); + break; + case kScrollbarHorizontalTrack: + case kScrollbarVerticalTrack: + PaintScrollbarTrack(canvas, hdc, part, state, rect, + extra.scrollbar_track); + break; + case kScrollbarHorizontalThumb: + case kScrollbarVerticalThumb: + case kScrollbarHorizontalGripper: + case kScrollbarVerticalGripper: + PaintScrollbarThumb(hdc, part, state, rect, extra.scrollbar_thumb); + break; + case kInnerSpinButton: + PaintSpinButton(hdc, part, state, rect, extra.inner_spin); + break; + case kTrackbarThumb: + case kTrackbarTrack: + PaintTrackbar(canvas, hdc, part, state, rect, extra.trackbar); + break; + case kProgressBar: + PaintProgressBar(hdc, rect, extra.progress_bar); + break; + case kWindowResizeGripper: + PaintWindowResizeGripper(hdc, rect); + break; + case kTabPanelBackground: + PaintTabPanelBackground(hdc, rect); + break; + case kTextField: + PaintTextField(hdc, part, state, rect, extra.text_field); + break; + + case kSliderTrack: + case kSliderThumb: + default: + // While transitioning NativeThemeWin to the single Paint() entry point, + // unsupported parts will DCHECK here. + NOTREACHED(); + } +} + +SkColor NativeThemeWin::GetSystemColor(ColorId color_id) const { + switch (color_id) { + + // Dialogs + case kColorId_DialogBackground: + // TODO(benrg): Should this use the new Windows theme functions? The old + // code in DialogClientView::OnPaint used GetSysColor(COLOR_3DFACE). + return WinColorToSkColor(GetSysColor(COLOR_3DFACE)); + + // FocusableBorder + case kColorId_FocusedBorderColor: + return kFocusedBorderColor; + case kColorId_UnfocusedBorderColor: + return kUnfocusedBorderColor; + + // TextButton + case kColorId_TextButtonBackgroundColor: + return kTextButtonBackgroundColor; + case kColorId_TextButtonEnabledColor: + return kTextButtonEnabledColor; + case kColorId_TextButtonDisabledColor: + return kTextButtonDisabledColor; + case kColorId_TextButtonHighlightColor: + return kTextButtonHighlightColor; + case kColorId_TextButtonHoverColor: + return kTextButtonHoverColor; + + // MenuItem + case kColorId_EnabledMenuItemForegroundColor: + return kEnabledMenuItemForegroundColor; + case kColorId_DisabledMenuItemForegroundColor: + return kDisabledMenuItemForegroundColor; + case kColorId_FocusedMenuItemBackgroundColor: + return kFocusedMenuItemBackgroundColor; + case kColorId_MenuSeparatorColor: + return kMenuSeparatorColor; + + // Label + case kColorId_LabelEnabledColor: + return kLabelEnabledColor; + case kColorId_LabelDisabledColor: + return kLabelDisabledColor; + case kColorId_LabelBackgroundColor: + return kLabelBackgroundColor; + + // Textfield + case kColorId_TextfieldDefaultColor: + return kTextfieldDefaultColor; + case kColorId_TextfieldDefaultBackground: + return kTextfieldDefaultBackground; + case kColorId_TextfieldSelectionColor: + return kTextfieldSelectionColor; + case kColorId_TextfieldSelectionBackgroundFocused: + return kTextfieldSelectionBackgroundFocused; + case kColorId_TextfieldSelectionBackgroundUnfocused: + return kTextfieldSelectionBackgroundUnfocused; + + default: + NOTREACHED() << "Invalid color_id: " << color_id; + break; + } + return kInvalidColorIdColor; +} + +void NativeThemeWin::PaintIndirect(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra) const { + // TODO(asvitkine): This path is pretty inefficient - for each paint operation + // it creates a new offscreen bitmap Skia canvas. This can + // be sped up by doing it only once per part/state and + // keeping a cache of the resulting bitmaps. + + // Create an offscreen canvas that is backed by an HDC. + skia::BitmapPlatformDevice* device = skia::BitmapPlatformDevice::Create( + rect.width(), rect.height(), false, NULL); + DCHECK(device); + SkCanvas offscreen_canvas(device); + device->unref(); + DCHECK(skia::SupportsPlatformPaint(&offscreen_canvas)); + + // Some of the Windows theme drawing operations do not write correct alpha + // values for fully-opaque pixels; instead the pixels get alpha 0. This is + // especially a problem on Windows XP or when using the Classic theme. + // + // To work-around this, mark all pixels with a placeholder value, to detect + // which pixels get touched by the paint operation. After paint, set any + // pixels that have alpha 0 to opaque and placeholders to fully-transparent. + const SkColor placeholder = SkColorSetARGB(1, 0, 0, 0); + offscreen_canvas.clear(placeholder); + + // Offset destination rects to have origin (0,0). + gfx::Rect adjusted_rect(rect.size()); + ExtraParams adjusted_extra(extra); + switch (part) { + case kProgressBar: + adjusted_extra.progress_bar.value_rect_x = 0; + adjusted_extra.progress_bar.value_rect_y = 0; + break; + case kScrollbarHorizontalTrack: + case kScrollbarVerticalTrack: + adjusted_extra.scrollbar_track.track_x = 0; + adjusted_extra.scrollbar_track.track_y = 0; + break; + default: break; + } + // Draw the theme controls using existing HDC-drawing code. + PaintDirect(&offscreen_canvas, + part, + state, + adjusted_rect, + adjusted_extra); + + // Copy the pixels to a bitmap that has ref-counted pixel storage, which is + // necessary to have when drawing to a SkPicture. + const SkBitmap& hdc_bitmap = + offscreen_canvas.getDevice()->accessBitmap(false); + SkBitmap bitmap; + hdc_bitmap.copyTo(&bitmap, SkBitmap::kARGB_8888_Config); + + // Post-process the pixels to fix up the alpha values (see big comment above). + const SkPMColor placeholder_value = SkPreMultiplyColor(placeholder); + const int pixel_count = rect.width() * rect.height(); + SkPMColor* pixels = bitmap.getAddr32(0, 0); + for (int i = 0; i < pixel_count; i++) { + if (pixels[i] == placeholder_value) { + // Pixel wasn't touched - make it fully transparent. + pixels[i] = SkPackARGB32(0, 0, 0, 0); + } else if (SkGetPackedA32(pixels[i]) == 0) { + // Pixel was touched but has incorrect alpha of 0, make it fully opaque. + pixels[i] = SkPackARGB32(0xFF, + SkGetPackedR32(pixels[i]), + SkGetPackedG32(pixels[i]), + SkGetPackedB32(pixels[i])); + } + } + + // Draw the offscreen bitmap to the destination canvas. + canvas->drawBitmap(bitmap, rect.x(), rect.y()); +} + +HRESULT NativeThemeWin::GetThemePartSize(ThemeName theme_name, + HDC hdc, + int part_id, + int state_id, + RECT* rect, + int ts, + SIZE* size) const { + HANDLE handle = GetThemeHandle(theme_name); + if (handle && get_theme_part_size_) + return get_theme_part_size_(handle, hdc, part_id, state_id, rect, ts, size); + + return E_NOTIMPL; +} + +HRESULT NativeThemeWin::PaintButton(HDC hdc, + State state, + const ButtonExtraParams& extra, + int part_id, + int state_id, + RECT* rect) const { + HANDLE handle = GetThemeHandle(BUTTON); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + + // Adjust classic_state based on part, state, and extras. + int classic_state = extra.classic_state; + switch (part_id) { + case BP_CHECKBOX: + classic_state |= DFCS_BUTTONCHECK; + break; + case BP_RADIOBUTTON: + classic_state |= DFCS_BUTTONRADIO; + break; + case BP_PUSHBUTTON: + classic_state |= DFCS_BUTTONPUSH; + break; + default: + NOTREACHED() << "Unknown part_id: " << part_id; + break; + } + + switch (state) { + case kDisabled: + classic_state |= DFCS_INACTIVE; + break; + case kPressed: + classic_state |= DFCS_PUSHED; + break; + case kNormal: + case kHovered: + break; + default: + NOTREACHED() << "Unknown state: " << state; + break; + } + + if (extra.checked) + classic_state |= DFCS_CHECKED; + + // Draw it manually. + // All pressed states have both low bits set, and no other states do. + const bool focused = ((state_id & ETS_FOCUSED) == ETS_FOCUSED); + const bool pressed = ((state_id & PBS_PRESSED) == PBS_PRESSED); + if ((BP_PUSHBUTTON == part_id) && (pressed || focused)) { + // BP_PUSHBUTTON has a focus rect drawn around the outer edge, and the + // button itself is shrunk by 1 pixel. + HBRUSH brush = GetSysColorBrush(COLOR_3DDKSHADOW); + if (brush) { + FrameRect(hdc, rect, brush); + InflateRect(rect, -1, -1); + } + } + DrawFrameControl(hdc, rect, DFC_BUTTON, classic_state); + + // Draw the focus rectangle (the dotted line box) only on buttons. For radio + // and checkboxes, we let webkit draw the focus rectangle (orange glow). + if ((BP_PUSHBUTTON == part_id) && focused) { + // The focus rect is inside the button. The exact number of pixels depends + // on whether we're in classic mode or using uxtheme. + if (handle && get_theme_content_rect_) { + get_theme_content_rect_(handle, hdc, part_id, state_id, rect, rect); + } else { + InflateRect(rect, -GetSystemMetrics(SM_CXEDGE), + -GetSystemMetrics(SM_CYEDGE)); + } + DrawFocusRect(hdc, rect); + } + + return S_OK; +} + +HRESULT NativeThemeWin::PaintMenuSeparator( + HDC hdc, + const gfx::Rect& rect, + const MenuSeparatorExtraParams& extra) const { + RECT rect_win = rect.ToRECT(); + + HANDLE handle = GetThemeHandle(MENU); + if (handle && draw_theme_) { + // Delta is needed for non-classic to move separator up slightly. + --rect_win.top; + --rect_win.bottom; + return draw_theme_(handle, hdc, MENU_POPUPSEPARATOR, MPI_NORMAL, &rect_win, + NULL); + } + + DrawEdge(hdc, &rect_win, EDGE_ETCHED, BF_TOP); + return S_OK; +} + +HRESULT NativeThemeWin::PaintMenuGutter(HDC hdc, + const gfx::Rect& rect) const { + RECT rect_win = rect.ToRECT(); + HANDLE handle = GetThemeHandle(MENU); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, MENU_POPUPGUTTER, MPI_NORMAL, &rect_win, + NULL); + return E_NOTIMPL; +} + +HRESULT NativeThemeWin::PaintMenuArrow(HDC hdc, + State state, + const gfx::Rect& rect, + const MenuArrowExtraParams& extra) + const { + int state_id = MSM_NORMAL; + if (state == kDisabled) + state_id = MSM_DISABLED; + + HANDLE handle = GetThemeHandle(MENU); + RECT rect_win = rect.ToRECT(); + if (handle && draw_theme_) { + if (extra.pointing_right) { + return draw_theme_(handle, hdc, MENU_POPUPSUBMENU, state_id, &rect_win, + NULL); + } else { + // There is no way to tell the uxtheme API to draw a left pointing arrow; + // it doesn't have a flag equivalent to DFCS_MENUARROWRIGHT. But they + // are needed for RTL locales on Vista. So use a memory DC and mirror + // the region with GDI's StretchBlt. + gfx::Rect r(rect); + base::win::ScopedCreateDC mem_dc(CreateCompatibleDC(hdc)); + base::win::ScopedBitmap mem_bitmap(CreateCompatibleBitmap(hdc, r.width(), + r.height())); + base::win::ScopedSelectObject select_bitmap(mem_dc, mem_bitmap); + // Copy and horizontally mirror the background from hdc into mem_dc. Use + // a negative-width source rect, starting at the rightmost pixel. + StretchBlt(mem_dc, 0, 0, r.width(), r.height(), + hdc, r.right()-1, r.y(), -r.width(), r.height(), SRCCOPY); + // Draw the arrow. + RECT theme_rect = {0, 0, r.width(), r.height()}; + HRESULT result = draw_theme_(handle, mem_dc, MENU_POPUPSUBMENU, + state_id, &theme_rect, NULL); + // Copy and mirror the result back into mem_dc. + StretchBlt(hdc, r.x(), r.y(), r.width(), r.height(), + mem_dc, r.width()-1, 0, -r.width(), r.height(), SRCCOPY); + return result; + } + } + + // For some reason, Windows uses the name DFCS_MENUARROWRIGHT to indicate a + // left pointing arrow. This makes the following 'if' statement slightly + // counterintuitive. + UINT pfc_state; + if (extra.pointing_right) + pfc_state = DFCS_MENUARROW; + else + pfc_state = DFCS_MENUARROWRIGHT; + return PaintFrameControl(hdc, rect, DFC_MENU, pfc_state, extra.is_selected, + state); +} + +HRESULT NativeThemeWin::PaintMenuBackground(HDC hdc, + const gfx::Rect& rect) const { + HANDLE handle = GetThemeHandle(MENU); + RECT rect_win = rect.ToRECT(); + if (handle && draw_theme_) { + HRESULT result = draw_theme_(handle, hdc, MENU_POPUPBACKGROUND, 0, + &rect_win, NULL); + FrameRect(hdc, &rect_win, GetSysColorBrush(COLOR_3DSHADOW)); + return result; + } + + FillRect(hdc, &rect_win, GetSysColorBrush(COLOR_MENU)); + DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT); + return S_OK; +} + +HRESULT NativeThemeWin::PaintMenuCheck( + HDC hdc, + State state, + const gfx::Rect& rect, + const MenuCheckExtraParams& extra) const { + HANDLE handle = GetThemeHandle(MENU); + int state_id; + if (extra.is_radio) { + state_id = state == kDisabled ? MC_BULLETDISABLED : MC_BULLETNORMAL; + } else { + state_id = state == kDisabled ? MC_CHECKMARKDISABLED : MC_CHECKMARKNORMAL; + } + + RECT rect_win = rect.ToRECT(); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, MENU_POPUPCHECK, state_id, &rect_win, NULL); + + return PaintFrameControl(hdc, rect, DFC_MENU, + extra.is_radio ? DFCS_MENUBULLET : DFCS_MENUCHECK, + extra.is_selected, state); +} + +HRESULT NativeThemeWin::PaintMenuCheckBackground(HDC hdc, + State state, + const gfx::Rect& rect) const { + HANDLE handle = GetThemeHandle(MENU); + int state_id = state == kDisabled ? MCB_DISABLED : MCB_NORMAL; + RECT rect_win = rect.ToRECT(); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, MENU_POPUPCHECKBACKGROUND, state_id, + &rect_win, NULL); + // Nothing to do for background. + return S_OK; +} + +HRESULT NativeThemeWin::PaintMenuItemBackground( + HDC hdc, + State state, + const gfx::Rect& rect, + const MenuItemExtraParams& extra) const { + HANDLE handle = GetThemeHandle(MENU); + RECT rect_win = rect.ToRECT(); + int state_id; + switch (state) { + case kNormal: + state_id = MPI_NORMAL; + break; + case kDisabled: + state_id = extra.is_selected ? MPI_DISABLEDHOT : MPI_DISABLED; + break; + case kHovered: + state_id = MPI_HOT; + break; + default: + NOTREACHED() << "Invalid state " << state; + break; + } + + if (handle && draw_theme_) + return draw_theme_(handle, hdc, MENU_POPUPITEM, state_id, &rect_win, NULL); + + if (extra.is_selected) + FillRect(hdc, &rect_win, GetSysColorBrush(COLOR_HIGHLIGHT)); + return S_OK; +} + +HRESULT NativeThemeWin::PaintPushButton(HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& extra) const { + int state_id; + switch (state) { + case kDisabled: + state_id = PBS_DISABLED; + break; + case kHovered: + state_id = PBS_HOT; + break; + case kNormal: + state_id = extra.is_default ? PBS_DEFAULTED : PBS_NORMAL; + break; + case kPressed: + state_id = PBS_PRESSED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + + RECT rect_win = rect.ToRECT(); + return PaintButton(hdc, state, extra, BP_PUSHBUTTON, state_id, &rect_win); +} + +HRESULT NativeThemeWin::PaintRadioButton(HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& extra) const { + int state_id; + switch (state) { + case kDisabled: + state_id = extra.checked ? RBS_CHECKEDDISABLED : RBS_UNCHECKEDDISABLED; + break; + case kHovered: + state_id = extra.checked ? RBS_CHECKEDHOT : RBS_UNCHECKEDHOT; + break; + case kNormal: + state_id = extra.checked ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL; + break; + case kPressed: + state_id = extra.checked ? RBS_CHECKEDPRESSED : RBS_UNCHECKEDPRESSED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + + RECT rect_win = rect.ToRECT(); + return PaintButton(hdc, state, extra, BP_RADIOBUTTON, state_id, &rect_win); +} + +HRESULT NativeThemeWin::PaintCheckbox(HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& extra) const { + int state_id; + switch (state) { + case kDisabled: + state_id = extra.checked ? CBS_CHECKEDDISABLED : + extra.indeterminate ? CBS_MIXEDDISABLED : + CBS_UNCHECKEDDISABLED; + break; + case kHovered: + state_id = extra.checked ? CBS_CHECKEDHOT : + extra.indeterminate ? CBS_MIXEDHOT : + CBS_UNCHECKEDHOT; + break; + case kNormal: + state_id = extra.checked ? CBS_CHECKEDNORMAL : + extra.indeterminate ? CBS_MIXEDNORMAL : + CBS_UNCHECKEDNORMAL; + break; + case kPressed: + state_id = extra.checked ? CBS_CHECKEDPRESSED : + extra.indeterminate ? CBS_MIXEDPRESSED : + CBS_UNCHECKEDPRESSED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + + RECT rect_win = rect.ToRECT(); + return PaintButton(hdc, state, extra, BP_CHECKBOX, state_id, &rect_win); +} + +HRESULT NativeThemeWin::PaintMenuList(HDC hdc, + State state, + const gfx::Rect& rect, + const MenuListExtraParams& extra) const { + HANDLE handle = GetThemeHandle(MENULIST); + RECT rect_win = rect.ToRECT(); + int state_id; + switch (state) { + case kNormal: + state_id = CBXS_NORMAL; + break; + case kDisabled: + state_id = CBXS_DISABLED; + break; + case kHovered: + state_id = CBXS_HOT; + break; + case kPressed: + state_id = CBXS_PRESSED; + break; + default: + NOTREACHED() << "Invalid state " << state; + break; + } + + if (handle && draw_theme_) + return draw_theme_(handle, hdc, CP_DROPDOWNBUTTON, state_id, &rect_win, + NULL); + + // Draw it manually. + DrawFrameControl(hdc, &rect_win, DFC_SCROLL, + DFCS_SCROLLCOMBOBOX | extra.classic_state); + return S_OK; +} + +HRESULT NativeThemeWin::PaintScrollbarArrow( + HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ScrollbarArrowExtraParams& extra) const { + static const int state_id_matrix[4][kMaxState] = { + ABS_DOWNDISABLED, ABS_DOWNHOT, ABS_DOWNNORMAL, ABS_DOWNPRESSED, + ABS_LEFTDISABLED, ABS_LEFTHOT, ABS_LEFTNORMAL, ABS_LEFTPRESSED, + ABS_RIGHTDISABLED, ABS_RIGHTHOT, ABS_RIGHTNORMAL, ABS_RIGHTPRESSED, + ABS_UPDISABLED, ABS_UPHOT, ABS_UPNORMAL, ABS_UPPRESSED + }; + HANDLE handle = GetThemeHandle(SCROLLBAR); + RECT rect_win = rect.ToRECT(); + if (handle && draw_theme_) { + int index = part - kScrollbarDownArrow; + DCHECK(index >=0 && index < 4); + int state_id = state_id_matrix[index][state]; + + // Hovering means that the cursor is over the scroolbar, but not over the + // specific arrow itself. We don't want to show it "hot" mode, but only + // in "hover" mode. + if (state == kHovered && extra.is_hovering) { + switch (part) { + case kScrollbarDownArrow: + state_id = ABS_DOWNHOVER; + break; + case kScrollbarLeftArrow: + state_id = ABS_LEFTHOVER; + break; + case kScrollbarRightArrow: + state_id = ABS_RIGHTHOVER; + break; + case kScrollbarUpArrow: + state_id = ABS_UPHOVER; + break; + default: + NOTREACHED() << "Invalid part: " << part; + break; + } + } + + return draw_theme_(handle, hdc, SBP_ARROWBTN, state_id, &rect_win, NULL); + } + + int classic_state = DFCS_SCROLLDOWN; + switch (part) { + case kScrollbarDownArrow: + classic_state = DFCS_SCROLLDOWN; + break; + case kScrollbarLeftArrow: + classic_state = DFCS_SCROLLLEFT; + break; + case kScrollbarRightArrow: + classic_state = DFCS_SCROLLRIGHT; + break; + case kScrollbarUpArrow: + classic_state = DFCS_SCROLLUP; + break; + default: + NOTREACHED() << "Invalid part: " << part; + break; + } + switch (state) { + case kDisabled: + classic_state |= DFCS_INACTIVE; + break; + case kHovered: + classic_state |= DFCS_HOT; + break; + case kNormal: + break; + case kPressed: + classic_state |= DFCS_PUSHED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + DrawFrameControl(hdc, &rect_win, DFC_SCROLL, classic_state); + return S_OK; +} + +HRESULT NativeThemeWin::PaintScrollbarThumb( + HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ScrollbarThumbExtraParams& extra) const { + HANDLE handle = GetThemeHandle(SCROLLBAR); + RECT rect_win = rect.ToRECT(); + int part_id; + int state_id; + + switch (part) { + case NativeTheme::kScrollbarHorizontalThumb: + part_id = SBP_THUMBBTNHORZ; + break; + case NativeTheme::kScrollbarVerticalThumb: + part_id = SBP_THUMBBTNVERT; + break; + case NativeTheme::kScrollbarHorizontalGripper: + part_id = SBP_GRIPPERHORZ; + break; + case NativeTheme::kScrollbarVerticalGripper: + part_id = SBP_GRIPPERVERT; + break; + default: + NOTREACHED() << "Invalid part: " << part; + break; + } + + switch (state) { + case kDisabled: + state_id = SCRBS_DISABLED; + break; + case kHovered: + state_id = extra.is_hovering ? SCRBS_HOVER : SCRBS_HOT; + break; + case kNormal: + state_id = SCRBS_NORMAL; + break; + case kPressed: + state_id = SCRBS_PRESSED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, &rect_win, NULL); + + // Draw it manually. + if ((part_id == SBP_THUMBBTNHORZ) || (part_id == SBP_THUMBBTNVERT)) + DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_MIDDLE); + // Classic mode doesn't have a gripper. + return S_OK; +} + +HRESULT NativeThemeWin::PaintScrollbarTrack( + SkCanvas* canvas, + HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ScrollbarTrackExtraParams& extra) const { + HANDLE handle = GetThemeHandle(SCROLLBAR); + RECT rect_win = rect.ToRECT(); + int part_id; + int state_id; + + switch (part) { + case NativeTheme::kScrollbarHorizontalTrack: + part_id = extra.is_upper ? SBP_UPPERTRACKHORZ : SBP_LOWERTRACKHORZ; + break; + case NativeTheme::kScrollbarVerticalTrack: + part_id = extra.is_upper ? SBP_UPPERTRACKVERT : SBP_LOWERTRACKVERT; + break; + default: + NOTREACHED() << "Invalid part: " << part; + break; + } + + switch (state) { + case kDisabled: + state_id = SCRBS_DISABLED; + break; + case kHovered: + state_id = SCRBS_HOVER; + break; + case kNormal: + state_id = SCRBS_NORMAL; + break; + case kPressed: + state_id = SCRBS_PRESSED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, &rect_win, NULL); + + // Draw it manually. + const DWORD colorScrollbar = GetSysColor(COLOR_SCROLLBAR); + const DWORD color3DFace = GetSysColor(COLOR_3DFACE); + if ((colorScrollbar != color3DFace) && + (colorScrollbar != GetSysColor(COLOR_WINDOW))) { + FillRect(hdc, &rect_win, reinterpret_cast<HBRUSH>(COLOR_SCROLLBAR + 1)); + } else { + SkPaint paint; + RECT align_rect = gfx::Rect(extra.track_x, extra.track_y, extra.track_width, + extra.track_height).ToRECT(); + SetCheckerboardShader(&paint, align_rect); + canvas->drawIRect(skia::RECTToSkIRect(rect_win), paint); + } + if (extra.classic_state & DFCS_PUSHED) + InvertRect(hdc, &rect_win); + return S_OK; +} + +HRESULT NativeThemeWin::PaintSpinButton( + HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const InnerSpinButtonExtraParams& extra) const { + HANDLE handle = GetThemeHandle(SPIN); + RECT rect_win = rect.ToRECT(); + int part_id = extra.spin_up ? SPNP_UP : SPNP_DOWN; + int state_id; + switch (state) { + case kDisabled: + state_id = extra.spin_up ? UPS_DISABLED : DNS_DISABLED; + break; + case kHovered: + state_id = extra.spin_up ? UPS_HOT : DNS_HOT; + break; + case kNormal: + state_id = extra.spin_up ? UPS_NORMAL : DNS_NORMAL; + break; + case kPressed: + state_id = extra.spin_up ? UPS_PRESSED : DNS_PRESSED; + break; + default: + NOTREACHED() << "Invalid state " << state; + break; + } + + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, &rect_win, NULL); + DrawFrameControl(hdc, &rect_win, DFC_SCROLL, extra.classic_state); + return S_OK; +} + +HRESULT NativeThemeWin::PaintTrackbar( + SkCanvas* canvas, + HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const TrackbarExtraParams& extra) const { + int part_id = part == kTrackbarTrack ? TKP_TRACK : TKP_THUMBBOTTOM; + if (extra.vertical) + part_id = part == kTrackbarTrack ? TKP_TRACKVERT : TKP_THUMBVERT; + + int state_id = 0; + switch (state) { + case kDisabled: + state_id = TUS_DISABLED; + break; + case kHovered: + state_id = TUS_HOT; + break; + case kNormal: + state_id = TUS_NORMAL; + break; + case kPressed: + state_id = TUS_PRESSED; + break; + default: + NOTREACHED() << "Invalid state " << state; + break; + } + + // Make the channel be 4 px thick in the center of the supplied rect. (4 px + // matches what XP does in various menus; GetThemePartSize() doesn't seem to + // return good values here.) + RECT rect_win = rect.ToRECT(); + RECT channel_rect = rect.ToRECT(); + const int channel_thickness = 4; + if (part_id == TKP_TRACK) { + channel_rect.top += + ((channel_rect.bottom - channel_rect.top - channel_thickness) / 2); + channel_rect.bottom = channel_rect.top + channel_thickness; + } else if (part_id == TKP_TRACKVERT) { + channel_rect.left += + ((channel_rect.right - channel_rect.left - channel_thickness) / 2); + channel_rect.right = channel_rect.left + channel_thickness; + } // else this isn't actually a channel, so |channel_rect| == |rect|. + + HANDLE handle = GetThemeHandle(TRACKBAR); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, &channel_rect, NULL); + + // Classic mode, draw it manually. + if ((part_id == TKP_TRACK) || (part_id == TKP_TRACKVERT)) { + DrawEdge(hdc, &channel_rect, EDGE_SUNKEN, BF_RECT); + } else if (part_id == TKP_THUMBVERT) { + DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE); + } else { + // Split rect into top and bottom pieces. + RECT top_section = rect.ToRECT(); + RECT bottom_section = rect.ToRECT(); + top_section.bottom -= ((bottom_section.right - bottom_section.left) / 2); + bottom_section.top = top_section.bottom; + DrawEdge(hdc, &top_section, EDGE_RAISED, + BF_LEFT | BF_TOP | BF_RIGHT | BF_SOFT | BF_MIDDLE | BF_ADJUST); + + // Split triangular piece into two diagonals. + RECT& left_half = bottom_section; + RECT right_half = bottom_section; + right_half.left += ((bottom_section.right - bottom_section.left) / 2); + left_half.right = right_half.left; + DrawEdge(hdc, &left_half, EDGE_RAISED, + BF_DIAGONAL_ENDTOPLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST); + DrawEdge(hdc, &right_half, EDGE_RAISED, + BF_DIAGONAL_ENDBOTTOMLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST); + + // If the button is pressed, draw hatching. + if (extra.classic_state & DFCS_PUSHED) { + SkPaint paint; + SetCheckerboardShader(&paint, rect_win); + + // Fill all three pieces with the pattern. + canvas->drawIRect(skia::RECTToSkIRect(top_section), paint); + + SkScalar left_triangle_top = SkIntToScalar(left_half.top); + SkScalar left_triangle_right = SkIntToScalar(left_half.right); + SkPath left_triangle; + left_triangle.moveTo(SkIntToScalar(left_half.left), left_triangle_top); + left_triangle.lineTo(left_triangle_right, left_triangle_top); + left_triangle.lineTo(left_triangle_right, + SkIntToScalar(left_half.bottom)); + left_triangle.close(); + canvas->drawPath(left_triangle, paint); + + SkScalar right_triangle_left = SkIntToScalar(right_half.left); + SkScalar right_triangle_top = SkIntToScalar(right_half.top); + SkPath right_triangle; + right_triangle.moveTo(right_triangle_left, right_triangle_top); + right_triangle.lineTo(SkIntToScalar(right_half.right), + right_triangle_top); + right_triangle.lineTo(right_triangle_left, + SkIntToScalar(right_half.bottom)); + right_triangle.close(); + canvas->drawPath(right_triangle, paint); + } + } + return S_OK; +} + +HRESULT NativeThemeWin::PaintProgressBar( + HDC hdc, + const gfx::Rect& rect, + const ProgressBarExtraParams& extra) const { + // There is no documentation about the animation speed, frame-rate, nor + // size of moving overlay of the indeterminate progress bar. + // So we just observed real-world programs and guessed following parameters. + const int kDeteminateOverlayPixelsPerSecond = 300; + const int kDeteminateOverlayWidth = 120; + const int kIndeterminateOverlayPixelsPerSecond = 175; + const int kVistaIndeterminateOverlayWidth = 120; + const int kXPIndeterminateOverlayWidth = 55; + // The thickness of the bar frame inside |value_rect| + const int kXPBarPadding = 3; + + RECT bar_rect = rect.ToRECT(); + RECT value_rect = gfx::Rect(extra.value_rect_x, + extra.value_rect_y, + extra.value_rect_width, + extra.value_rect_height).ToRECT(); + + bool pre_vista = base::win::GetVersion() < base::win::VERSION_VISTA; + HANDLE handle = GetThemeHandle(PROGRESS); + if (handle && draw_theme_ && draw_theme_ex_) { + draw_theme_(handle, hdc, PP_BAR, 0, &bar_rect, NULL); + + int bar_width = bar_rect.right - bar_rect.left; + if (extra.determinate) { + // TODO(morrita): this RTL guess can be wrong. + // We should pass the direction from WebKit side. + bool is_rtl = (bar_rect.right == value_rect.right && + bar_rect.left != value_rect.left); + // We should care the direction here because PP_CNUNK painting + // is asymmetric. + DTBGOPTS value_draw_options; + value_draw_options.dwSize = sizeof(DTBGOPTS); + value_draw_options.dwFlags = is_rtl ? DTBG_MIRRORDC : 0; + value_draw_options.rcClip = bar_rect; + + if (pre_vista) { + // On XP, progress bar is chunk-style and has no glossy effect. + // We need to shrink destination rect to fit the part inside the bar + // with an appropriate margin. + RECT shrunk_value_rect = InsetRect(&value_rect, kXPBarPadding); + draw_theme_ex_(handle, hdc, PP_CHUNK, 0, + &shrunk_value_rect, &value_draw_options); + } else { + // On Vista or later, the progress bar part has a + // single-block value part. It also has glossy effect. + // And the value part has exactly same height as the bar part + // so we don't need to shrink the rect. + draw_theme_ex_(handle, hdc, PP_FILL, 0, + &value_rect, &value_draw_options); + + int dx = ComputeAnimationProgress(bar_width, + kDeteminateOverlayWidth, + kDeteminateOverlayPixelsPerSecond, + extra.animated_seconds); + RECT overlay_rect = value_rect; + overlay_rect.left += dx; + overlay_rect.right = overlay_rect.left + kDeteminateOverlayWidth; + draw_theme_(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect, &value_rect); + } + } else { + // A glossy overlay for indeterminate progress bar has small pause + // after each animation. We emulate this by adding an invisible margin + // the animation has to traverse. + int width_with_margin = bar_width + kIndeterminateOverlayPixelsPerSecond; + int overlay_width = pre_vista ? + kXPIndeterminateOverlayWidth : kVistaIndeterminateOverlayWidth; + int dx = ComputeAnimationProgress(width_with_margin, + overlay_width, + kIndeterminateOverlayPixelsPerSecond, + extra.animated_seconds); + RECT overlay_rect = bar_rect; + overlay_rect.left += dx; + overlay_rect.right = overlay_rect.left + overlay_width; + if (pre_vista) { + RECT shrunk_rect = InsetRect(&overlay_rect, kXPBarPadding); + RECT shrunk_bar_rect = InsetRect(&bar_rect, kXPBarPadding); + draw_theme_(handle, hdc, PP_CHUNK, 0, &shrunk_rect, &shrunk_bar_rect); + } else { + draw_theme_(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect, &bar_rect); + } + } + + return S_OK; + } + + HBRUSH bg_brush = GetSysColorBrush(COLOR_BTNFACE); + HBRUSH fg_brush = GetSysColorBrush(COLOR_BTNSHADOW); + FillRect(hdc, &bar_rect, bg_brush); + FillRect(hdc, &value_rect, fg_brush); + DrawEdge(hdc, &bar_rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + return S_OK; +} + +HRESULT NativeThemeWin::PaintWindowResizeGripper(HDC hdc, + const gfx::Rect& rect) const { + HANDLE handle = GetThemeHandle(STATUS); + RECT rect_win = rect.ToRECT(); + if (handle && draw_theme_) { + // Paint the status bar gripper. There doesn't seem to be a + // standard gripper in Windows for the space between + // scrollbars. This is pretty close, but it's supposed to be + // painted over a status bar. + return draw_theme_(handle, hdc, SP_GRIPPER, 0, &rect_win, NULL); + } + + // Draw a windows classic scrollbar gripper. + DrawFrameControl(hdc, &rect_win, DFC_SCROLL, DFCS_SCROLLSIZEGRIP); + return S_OK; +} + +HRESULT NativeThemeWin::PaintTabPanelBackground(HDC hdc, + const gfx::Rect& rect) const { + HANDLE handle = GetThemeHandle(TAB); + RECT rect_win = rect.ToRECT(); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, TABP_BODY, 0, &rect_win, NULL); + + // Classic just renders a flat color background. + FillRect(hdc, &rect_win, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1)); + return S_OK; +} + +HRESULT NativeThemeWin::PaintTextField( + HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const TextFieldExtraParams& extra) const { + int part_id = EP_EDITTEXT; + int state_id = ETS_NORMAL; + switch (state) { + case kNormal: + if (extra.is_read_only) { + state_id = ETS_READONLY; + } else if (extra.is_focused) { + state_id = ETS_FOCUSED; + } else { + state_id = ETS_NORMAL; + } + break; + case kHovered: + state_id = ETS_HOT; + break; + case kPressed: + state_id = ETS_SELECTED; + break; + case kDisabled: + state_id = ETS_DISABLED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + + RECT rect_win = rect.ToRECT(); + return PaintTextField(hdc, part_id, state_id, extra.classic_state, + &rect_win, + skia::SkColorToCOLORREF(extra.background_color), + extra.fill_content_area, extra.draw_edges); +} + +HRESULT NativeThemeWin::PaintTextField(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect, + COLORREF color, + bool fill_content_area, + bool draw_edges) const { + // TODO(ojan): http://b/1210017 Figure out how to give the ability to + // exclude individual edges from being drawn. + + HANDLE handle = GetThemeHandle(TEXTFIELD); + // TODO(mpcomplete): can we detect if the color is specified by the user, + // and if not, just use the system color? + // CreateSolidBrush() accepts a RGB value but alpha must be 0. + HBRUSH bg_brush = CreateSolidBrush(color); + HRESULT hr; + // DrawThemeBackgroundEx was introduced in XP SP2, so that it's possible + // draw_theme_ex_ is NULL and draw_theme_ is non-null. + if (handle && (draw_theme_ex_ || (draw_theme_ && draw_edges))) { + if (draw_theme_ex_) { + static const DTBGOPTS omit_border_options = { + sizeof(DTBGOPTS), + DTBG_OMITBORDER, + { 0, 0, 0, 0 } + }; + const DTBGOPTS* draw_opts = draw_edges ? NULL : &omit_border_options; + hr = draw_theme_ex_(handle, hdc, part_id, state_id, rect, draw_opts); + } else { + hr = draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + } + + // TODO(maruel): Need to be fixed if get_theme_content_rect_ is NULL. + if (fill_content_area && get_theme_content_rect_) { + RECT content_rect; + hr = get_theme_content_rect_(handle, hdc, part_id, state_id, rect, + &content_rect); + FillRect(hdc, &content_rect, bg_brush); + } + } else { + // Draw it manually. + if (draw_edges) + DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + + if (fill_content_area) { + FillRect(hdc, rect, (classic_state & DFCS_INACTIVE) ? + reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1) : bg_brush); + } + hr = S_OK; + } + DeleteObject(bg_brush); + return hr; +} + +// static +NativeThemeWin::ThemeName NativeThemeWin::GetThemeName(Part part) { + ThemeName name; + switch (part) { + case kCheckbox: + case kRadio: + case kPushButton: + name = BUTTON; + break; + case kInnerSpinButton: + name = SPIN; + break; + case kMenuCheck: + case kMenuPopupGutter: + case kMenuList: + case kMenuPopupArrow: + case kMenuPopupSeparator: + name = MENU; + break; + case kProgressBar: + name = PROGRESS; + break; + case kScrollbarDownArrow: + case kScrollbarLeftArrow: + case kScrollbarRightArrow: + case kScrollbarUpArrow: + case kScrollbarHorizontalThumb: + case kScrollbarVerticalThumb: + case kScrollbarHorizontalTrack: + case kScrollbarVerticalTrack: + name = SCROLLBAR; + break; + case kSliderTrack: + case kSliderThumb: + name = TRACKBAR; + break; + case kTextField: + name = TEXTFIELD; + break; + case kWindowResizeGripper: + name = STATUS; + break; + default: + NOTREACHED() << "Invalid part: " << part; + break; + } + return name; +} + +// static +int NativeThemeWin::GetWindowsPart(Part part, + State state, + const ExtraParams& extra) { + int part_id; + switch (part) { + case kCheckbox: + part_id = BP_CHECKBOX; + break; + case kMenuCheck: + part_id = MENU_POPUPCHECK; + break; + case kMenuPopupArrow: + part_id = MENU_POPUPSUBMENU; + break; + case kMenuPopupGutter: + part_id = MENU_POPUPGUTTER; + break; + case kMenuPopupSeparator: + part_id = MENU_POPUPSEPARATOR; + break; + case kPushButton: + part_id = BP_PUSHBUTTON; + break; + case kRadio: + part_id = BP_RADIOBUTTON; + break; + case kWindowResizeGripper: + part_id = SP_GRIPPER; + break; + case kScrollbarDownArrow: + case kScrollbarLeftArrow: + case kScrollbarRightArrow: + case kScrollbarUpArrow: + part_id = SBP_ARROWBTN; + break; + case kScrollbarHorizontalThumb: + part_id = extra.scrollbar_track.is_upper ? SBP_UPPERTRACKHORZ : + SBP_LOWERTRACKHORZ; + break; + case kScrollbarVerticalThumb: + part_id = extra.scrollbar_track.is_upper ? SBP_UPPERTRACKVERT : + SBP_LOWERTRACKVERT; + break; + default: + NOTREACHED() << "Invalid part: " << part; + break; + } + return part_id; +} + +int NativeThemeWin::GetWindowsState(Part part, + State state, + const ExtraParams& extra) { + int state_id; + switch (part) { + case kCheckbox: + switch (state) { + case kNormal: + state_id = CBS_UNCHECKEDNORMAL; + break; + case kHovered: + state_id = CBS_UNCHECKEDHOT; + break; + case kPressed: + state_id = CBS_UNCHECKEDPRESSED; + break; + case kDisabled: + state_id = CBS_UNCHECKEDDISABLED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + break; + case kMenuCheck: + switch (state) { + case kNormal: + case kHovered: + case kPressed: + state_id = extra.menu_check.is_radio ? MC_BULLETNORMAL + : MC_CHECKMARKNORMAL; + break; + case kDisabled: + state_id = extra.menu_check.is_radio ? MC_BULLETDISABLED + : MC_CHECKMARKDISABLED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + break; + case kMenuPopupArrow: + case kMenuPopupGutter: + case kMenuPopupSeparator: + switch (state) { + case kNormal: + state_id = MBI_NORMAL; + break; + case kHovered: + state_id = MBI_HOT; + break; + case kPressed: + state_id = MBI_PUSHED; + break; + case kDisabled: + state_id = MBI_DISABLED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + break; + case kPushButton: + switch (state) { + case kNormal: + state_id = PBS_NORMAL; + break; + case kHovered: + state_id = PBS_HOT; + break; + case kPressed: + state_id = PBS_PRESSED; + break; + case kDisabled: + state_id = PBS_DISABLED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + break; + case kRadio: + switch (state) { + case kNormal: + state_id = RBS_UNCHECKEDNORMAL; + break; + case kHovered: + state_id = RBS_UNCHECKEDHOT; + break; + case kPressed: + state_id = RBS_UNCHECKEDPRESSED; + break; + case kDisabled: + state_id = RBS_UNCHECKEDDISABLED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + break; + case kWindowResizeGripper: + switch (state) { + case kNormal: + case kHovered: + case kPressed: + case kDisabled: + state_id = 1; // gripper has no windows state + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + break; + case kScrollbarDownArrow: + switch (state) { + case kNormal: + state_id = ABS_DOWNNORMAL; + break; + case kHovered: + // Mimic ScrollbarThemeChromiumWin.cpp in WebKit. + state_id = base::win::GetVersion() < base::win::VERSION_VISTA ? + ABS_DOWNHOT : ABS_DOWNHOVER; + break; + case kPressed: + state_id = ABS_DOWNPRESSED; + break; + case kDisabled: + state_id = ABS_DOWNDISABLED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + break; + case kScrollbarLeftArrow: + switch (state) { + case kNormal: + state_id = ABS_LEFTNORMAL; + break; + case kHovered: + // Mimic ScrollbarThemeChromiumWin.cpp in WebKit. + state_id = base::win::GetVersion() < base::win::VERSION_VISTA ? + ABS_LEFTHOT : ABS_LEFTHOVER; + break; + case kPressed: + state_id = ABS_LEFTPRESSED; + break; + case kDisabled: + state_id = ABS_LEFTDISABLED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + break; + case kScrollbarRightArrow: + switch (state) { + case kNormal: + state_id = ABS_RIGHTNORMAL; + break; + case kHovered: + // Mimic ScrollbarThemeChromiumWin.cpp in WebKit. + state_id = base::win::GetVersion() < base::win::VERSION_VISTA ? + ABS_RIGHTHOT : ABS_RIGHTHOVER; + break; + case kPressed: + state_id = ABS_RIGHTPRESSED; + break; + case kDisabled: + state_id = ABS_RIGHTDISABLED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + break; + case kScrollbarUpArrow: + switch (state) { + case kNormal: + state_id = ABS_UPNORMAL; + break; + case kHovered: + // Mimic ScrollbarThemeChromiumWin.cpp in WebKit. + state_id = base::win::GetVersion() < base::win::VERSION_VISTA ? + ABS_UPHOT : ABS_UPHOVER; + break; + case kPressed: + state_id = ABS_UPPRESSED; + break; + case kDisabled: + state_id = ABS_UPDISABLED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + break; + case kScrollbarHorizontalThumb: + case kScrollbarVerticalThumb: + switch (state) { + case kNormal: + state_id = SCRBS_NORMAL; + break; + case kHovered: + // Mimic WebKit's behaviour in ScrollbarThemeChromiumWin.cpp. + state_id = base::win::GetVersion() < base::win::VERSION_VISTA ? + SCRBS_HOT : SCRBS_HOVER; + break; + case kPressed: + state_id = SCRBS_PRESSED; + break; + case kDisabled: + state_id = SCRBS_DISABLED; + break; + default: + NOTREACHED() << "Invalid state: " << state; + break; + } + break; + default: + NOTREACHED() << "Invalid part: " << part; + break; + } + return state_id; +} + +HRESULT NativeThemeWin::GetThemeInt(ThemeName theme, + int part_id, + int state_id, + int prop_id, + int *value) const { + HANDLE handle = GetThemeHandle(theme); + if (handle && get_theme_int_) + return get_theme_int_(handle, part_id, state_id, prop_id, value); + return E_NOTIMPL; +} + +HRESULT NativeThemeWin::PaintFrameControl(HDC hdc, + const gfx::Rect& rect, + UINT type, + UINT state, + bool is_selected, + State control_state) const { + const int width = rect.width(); + const int height = rect.height(); + + // DrawFrameControl for menu arrow/check wants a monochrome bitmap. + base::win::ScopedBitmap mask_bitmap(CreateBitmap(width, height, 1, 1, NULL)); + + if (mask_bitmap == NULL) + return E_OUTOFMEMORY; + + base::win::ScopedCreateDC bitmap_dc(CreateCompatibleDC(NULL)); + base::win::ScopedSelectObject select_bitmap(bitmap_dc, mask_bitmap); + RECT local_rect = { 0, 0, width, height }; + DrawFrameControl(bitmap_dc, &local_rect, type, state); + + // We're going to use BitBlt with a b&w mask. This results in using the dest + // dc's text color for the black bits in the mask, and the dest dc's + // background color for the white bits in the mask. DrawFrameControl draws the + // check in black, and the background in white. + int bg_color_key; + int text_color_key; + switch (control_state) { + case NativeTheme::kHovered: + bg_color_key = COLOR_HIGHLIGHT; + text_color_key = COLOR_HIGHLIGHTTEXT; + break; + case NativeTheme::kNormal: + bg_color_key = COLOR_MENU; + text_color_key = COLOR_MENUTEXT; + break; + case NativeTheme::kDisabled: + bg_color_key = is_selected ? COLOR_HIGHLIGHT : COLOR_MENU; + text_color_key = COLOR_GRAYTEXT; + break; + default: + NOTREACHED(); + bg_color_key = COLOR_MENU; + text_color_key = COLOR_MENUTEXT; + break; + } + COLORREF old_bg_color = SetBkColor(hdc, GetSysColor(bg_color_key)); + COLORREF old_text_color = SetTextColor(hdc, GetSysColor(text_color_key)); + BitBlt(hdc, rect.x(), rect.y(), width, height, bitmap_dc, 0, 0, SRCCOPY); + SetBkColor(hdc, old_bg_color); + SetTextColor(hdc, old_text_color); + + return S_OK; +} + +HANDLE NativeThemeWin::GetThemeHandle(ThemeName theme_name) const { + if (!open_theme_ || theme_name < 0 || theme_name >= LAST) + return 0; + + if (theme_handles_[theme_name]) + return theme_handles_[theme_name]; + + // Not found, try to load it. + HANDLE handle = 0; + switch (theme_name) { + case BUTTON: + handle = open_theme_(NULL, L"Button"); + break; + case LIST: + handle = open_theme_(NULL, L"Listview"); + break; + case MENU: + handle = open_theme_(NULL, L"Menu"); + break; + case MENULIST: + handle = open_theme_(NULL, L"Combobox"); + break; + case SCROLLBAR: + handle = open_theme_(NULL, L"Scrollbar"); + break; + case STATUS: + handle = open_theme_(NULL, L"Status"); + break; + case TAB: + handle = open_theme_(NULL, L"Tab"); + break; + case TEXTFIELD: + handle = open_theme_(NULL, L"Edit"); + break; + case TRACKBAR: + handle = open_theme_(NULL, L"Trackbar"); + break; + case WINDOW: + handle = open_theme_(NULL, L"Window"); + break; + case PROGRESS: + handle = open_theme_(NULL, L"Progress"); + break; + case SPIN: + handle = open_theme_(NULL, L"Spin"); + break; + default: + NOTREACHED(); + } + theme_handles_[theme_name] = handle; + return handle; +} + +} // namespace ui |