diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /widget/windows/nsNativeThemeWin.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'widget/windows/nsNativeThemeWin.cpp')
-rw-r--r-- | widget/windows/nsNativeThemeWin.cpp | 4168 |
1 files changed, 4168 insertions, 0 deletions
diff --git a/widget/windows/nsNativeThemeWin.cpp b/widget/windows/nsNativeThemeWin.cpp new file mode 100644 index 000000000..4ff6b0af9 --- /dev/null +++ b/widget/windows/nsNativeThemeWin.cpp @@ -0,0 +1,4168 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNativeThemeWin.h" + +#include "mozilla/EventStates.h" +#include "mozilla/Logging.h" +#include "mozilla/WindowsVersion.h" +#include "nsDeviceContext.h" +#include "nsRenderingContext.h" +#include "nsRect.h" +#include "nsSize.h" +#include "nsTransform2D.h" +#include "nsThemeConstants.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "nsNameSpaceManager.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsLookAndFeel.h" +#include "nsMenuFrame.h" +#include "nsGkAtoms.h" +#include <malloc.h> +#include "nsWindow.h" +#include "nsIComboboxControlFrame.h" +#include "prinrval.h" +#include "WinUtils.h" + +#include "gfxPlatform.h" +#include "gfxContext.h" +#include "gfxWindowsPlatform.h" +#include "gfxWindowsSurface.h" +#include "gfxWindowsNativeDrawing.h" + +#include "nsUXThemeData.h" +#include "nsUXThemeConstants.h" +#include <algorithm> + +using mozilla::IsVistaOrLater; +using namespace mozilla; +using namespace mozilla::widget; + +extern mozilla::LazyLogModule gWindowsLog; + +NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeWin, nsNativeTheme, nsITheme) + +nsNativeThemeWin::nsNativeThemeWin() : + mProgressDeterminateTimeStamp(TimeStamp::Now()), + mProgressIndeterminateTimeStamp(TimeStamp::Now()) +{ + // If there is a relevant change in forms.css for windows platform, + // static widget style variables (e.g. sButtonBorderSize) should be + // reinitialized here. +} + +nsNativeThemeWin::~nsNativeThemeWin() +{ + nsUXThemeData::Invalidate(); +} + +static int32_t +GetTopLevelWindowActiveState(nsIFrame *aFrame) +{ + // Used by window frame and button box rendering. We can end up in here in + // the content process when rendering one of these moz styles freely in a + // page. Bail in this case, there is no applicable window focus state. + if (!XRE_IsParentProcess()) { + return mozilla::widget::themeconst::FS_INACTIVE; + } + // Get the widget. nsIFrame's GetNearestWidget walks up the view chain + // until it finds a real window. + nsIWidget* widget = aFrame->GetNearestWidget(); + nsWindowBase * window = static_cast<nsWindowBase*>(widget); + if (!window) + return mozilla::widget::themeconst::FS_INACTIVE; + if (widget && !window->IsTopLevelWidget() && + !(window = window->GetParentWindowBase(false))) + return mozilla::widget::themeconst::FS_INACTIVE; + + if (window->GetWindowHandle() == ::GetActiveWindow()) + return mozilla::widget::themeconst::FS_ACTIVE; + return mozilla::widget::themeconst::FS_INACTIVE; +} + +static int32_t +GetWindowFrameButtonState(nsIFrame* aFrame, EventStates eventState) +{ + if (GetTopLevelWindowActiveState(aFrame) == + mozilla::widget::themeconst::FS_INACTIVE) { + if (eventState.HasState(NS_EVENT_STATE_HOVER)) + return mozilla::widget::themeconst::BS_HOT; + return mozilla::widget::themeconst::BS_INACTIVE; + } + + if (eventState.HasState(NS_EVENT_STATE_HOVER)) { + if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) + return mozilla::widget::themeconst::BS_PUSHED; + return mozilla::widget::themeconst::BS_HOT; + } + return mozilla::widget::themeconst::BS_NORMAL; +} + +static int32_t +GetClassicWindowFrameButtonState(EventStates eventState) +{ + if (eventState.HasState(NS_EVENT_STATE_ACTIVE) && + eventState.HasState(NS_EVENT_STATE_HOVER)) + return DFCS_BUTTONPUSH|DFCS_PUSHED; + return DFCS_BUTTONPUSH; +} + +static bool +IsTopLevelMenu(nsIFrame *aFrame) +{ + bool isTopLevel(false); + nsMenuFrame *menuFrame = do_QueryFrame(aFrame); + if (menuFrame) { + isTopLevel = menuFrame->IsOnMenuBar(); + } + return isTopLevel; +} + +static MARGINS +GetCheckboxMargins(HANDLE theme, HDC hdc) +{ + MARGINS checkboxContent = {0}; + GetThemeMargins(theme, hdc, MENU_POPUPCHECK, MCB_NORMAL, + TMT_CONTENTMARGINS, nullptr, &checkboxContent); + return checkboxContent; +} + +static SIZE +GetCheckboxBGSize(HANDLE theme, HDC hdc) +{ + SIZE checkboxSize; + GetThemePartSize(theme, hdc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, + nullptr, TS_TRUE, &checkboxSize); + + MARGINS checkboxMargins = GetCheckboxMargins(theme, hdc); + + int leftMargin = checkboxMargins.cxLeftWidth; + int rightMargin = checkboxMargins.cxRightWidth; + int topMargin = checkboxMargins.cyTopHeight; + int bottomMargin = checkboxMargins.cyBottomHeight; + + int width = leftMargin + checkboxSize.cx + rightMargin; + int height = topMargin + checkboxSize.cy + bottomMargin; + SIZE ret; + ret.cx = width; + ret.cy = height; + return ret; +} + +static SIZE +GetCheckboxBGBounds(HANDLE theme, HDC hdc) +{ + MARGINS checkboxBGSizing = {0}; + MARGINS checkboxBGContent = {0}; + GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL, + TMT_SIZINGMARGINS, nullptr, &checkboxBGSizing); + GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL, + TMT_CONTENTMARGINS, nullptr, &checkboxBGContent); + +#define posdx(d) ((d) > 0 ? d : 0) + + int dx = posdx(checkboxBGContent.cxRightWidth - + checkboxBGSizing.cxRightWidth) + + posdx(checkboxBGContent.cxLeftWidth - + checkboxBGSizing.cxLeftWidth); + int dy = posdx(checkboxBGContent.cyTopHeight - + checkboxBGSizing.cyTopHeight) + + posdx(checkboxBGContent.cyBottomHeight - + checkboxBGSizing.cyBottomHeight); + +#undef posdx + + SIZE ret(GetCheckboxBGSize(theme, hdc)); + ret.cx += dx; + ret.cy += dy; + return ret; +} + +static SIZE +GetGutterSize(HANDLE theme, HDC hdc) +{ + SIZE gutterSize; + GetThemePartSize(theme, hdc, MENU_POPUPGUTTER, 0, nullptr, TS_TRUE, &gutterSize); + + SIZE checkboxBGSize(GetCheckboxBGBounds(theme, hdc)); + + SIZE itemSize; + GetThemePartSize(theme, hdc, MENU_POPUPITEM, MPI_NORMAL, nullptr, TS_TRUE, &itemSize); + + // Figure out how big the menuitem's icon will be (if present) at current DPI + double scaleFactor = nsIWidget::DefaultScaleOverride(); + if (scaleFactor <= 0.0) { + scaleFactor = WinUtils::LogToPhysFactor(hdc); + } + int iconDevicePixels = NSToIntRound(16 * scaleFactor); + SIZE iconSize = { + iconDevicePixels, iconDevicePixels + }; + // Not really sure what margins should be used here, but this seems to work in practice... + MARGINS margins = {0}; + GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL, + TMT_CONTENTMARGINS, nullptr, &margins); + iconSize.cx += margins.cxLeftWidth + margins.cxRightWidth; + iconSize.cy += margins.cyTopHeight + margins.cyBottomHeight; + + int width = std::max(itemSize.cx, std::max(iconSize.cx, checkboxBGSize.cx) + gutterSize.cx); + int height = std::max(itemSize.cy, std::max(iconSize.cy, checkboxBGSize.cy)); + + SIZE ret; + ret.cx = width; + ret.cy = height; + return ret; +} + +/* DrawThemeBGRTLAware - render a theme part based on rtl state. + * Some widgets are not direction-neutral and need to be drawn reversed for + * RTL. Windows provides a way to do this with SetLayout, but this reverses + * the entire drawing area of a given device context, which means that its + * use will also affect the positioning of the widget. There are two ways + * to work around this: + * + * Option 1: Alter the position of the rect that we send so that we cancel + * out the positioning effects of SetLayout + * Option 2: Create a memory DC with the widgetRect's dimensions, draw onto + * that, and then transfer the results back to our DC + * + * This function tries to implement option 1, under the assumption that the + * correct way to reverse the effects of SetLayout is to translate the rect + * such that the offset from the DC bitmap's left edge to the old rect's + * left edge is equal to the offset from the DC bitmap's right edge to the + * new rect's right edge. In other words, + * (oldRect.left + vpOrg.x) == ((dcBMP.width - vpOrg.x) - newRect.right) + */ +static HRESULT +DrawThemeBGRTLAware(HANDLE aTheme, HDC aHdc, int aPart, int aState, + const RECT *aWidgetRect, const RECT *aClipRect, + bool aIsRtl) +{ + NS_ASSERTION(aTheme, "Bad theme handle."); + NS_ASSERTION(aHdc, "Bad hdc."); + NS_ASSERTION(aWidgetRect, "Bad rect."); + NS_ASSERTION(aClipRect, "Bad clip rect."); + + if (!aIsRtl) { + return DrawThemeBackground(aTheme, aHdc, aPart, aState, + aWidgetRect, aClipRect); + } + + HGDIOBJ hObj = GetCurrentObject(aHdc, OBJ_BITMAP); + BITMAP bitmap; + POINT vpOrg; + + if (hObj && GetObject(hObj, sizeof(bitmap), &bitmap) && + GetViewportOrgEx(aHdc, &vpOrg)) { + RECT newWRect(*aWidgetRect); + newWRect.left = bitmap.bmWidth - (aWidgetRect->right + 2*vpOrg.x); + newWRect.right = bitmap.bmWidth - (aWidgetRect->left + 2*vpOrg.x); + + RECT newCRect; + RECT *newCRectPtr = nullptr; + + if (aClipRect) { + newCRect.top = aClipRect->top; + newCRect.bottom = aClipRect->bottom; + newCRect.left = bitmap.bmWidth - (aClipRect->right + 2*vpOrg.x); + newCRect.right = bitmap.bmWidth - (aClipRect->left + 2*vpOrg.x); + newCRectPtr = &newCRect; + } + + SetLayout(aHdc, LAYOUT_RTL); + HRESULT hr = DrawThemeBackground(aTheme, aHdc, aPart, aState, &newWRect, + newCRectPtr); + SetLayout(aHdc, 0); + if (SUCCEEDED(hr)) { + return hr; + } + } + return DrawThemeBackground(aTheme, aHdc, aPart, aState, + aWidgetRect, aClipRect); +} + +/* + * Caption button padding data - 'hot' button padding. + * These areas are considered hot, in that they activate + * a button when hovered or clicked. The button graphic + * is drawn inside the padding border. Unrecognized themes + * are treated as their recognized counterparts for now. + * left top right bottom + * classic min 1 2 0 1 + * classic max 0 2 1 1 + * classic close 1 2 2 1 + * + * aero basic min 1 2 0 2 + * aero basic max 0 2 1 2 + * aero basic close 1 2 1 2 + * + * xp theme min 0 2 0 2 + * xp theme max 0 2 1 2 + * xp theme close 1 2 2 2 + * + * 'cold' button padding - generic button padding, should + * be handled in css. + * left top right bottom + * classic min 0 0 0 0 + * classic max 0 0 0 0 + * classic close 0 0 0 0 + * + * aero basic min 0 0 1 0 + * aero basic max 1 0 0 0 + * aero basic close 0 0 0 0 + * + * xp theme min 0 0 1 0 + * xp theme max 1 0 0 0 + * xp theme close 0 0 0 0 + */ + +enum CaptionDesktopTheme { + CAPTION_CLASSIC = 0, + CAPTION_BASIC, + CAPTION_XPTHEME, +}; + +enum CaptionButton { + CAPTIONBUTTON_MINIMIZE = 0, + CAPTIONBUTTON_RESTORE, + CAPTIONBUTTON_CLOSE, +}; + +struct CaptionButtonPadding { + RECT hotPadding[3]; +}; + +// RECT: left, top, right, bottom +static CaptionButtonPadding buttonData[3] = { + { + { { 1, 2, 0, 1 }, { 0, 2, 1, 1 }, { 1, 2, 2, 1 } } + }, + { + { { 1, 2, 0, 2 }, { 0, 2, 1, 2 }, { 1, 2, 2, 2 } } + }, + { + { { 0, 2, 0, 2 }, { 0, 2, 1, 2 }, { 1, 2, 2, 2 } } + } +}; + +// Adds "hot" caption button padding to minimum widget size. +static void +AddPaddingRect(LayoutDeviceIntSize* aSize, CaptionButton button) { + if (!aSize) + return; + RECT offset; + if (!IsAppThemed()) + offset = buttonData[CAPTION_CLASSIC].hotPadding[button]; + else if (!IsVistaOrLater()) + offset = buttonData[CAPTION_XPTHEME].hotPadding[button]; + else + offset = buttonData[CAPTION_BASIC].hotPadding[button]; + aSize->width += offset.left + offset.right; + aSize->height += offset.top + offset.bottom; +} + +// If we've added padding to the minimum widget size, offset +// the area we draw into to compensate. +static void +OffsetBackgroundRect(RECT& rect, CaptionButton button) { + RECT offset; + if (!IsAppThemed()) + offset = buttonData[CAPTION_CLASSIC].hotPadding[button]; + else if (!IsVistaOrLater()) + offset = buttonData[CAPTION_XPTHEME].hotPadding[button]; + else + offset = buttonData[CAPTION_BASIC].hotPadding[button]; + rect.left += offset.left; + rect.top += offset.top; + rect.right -= offset.right; + rect.bottom -= offset.bottom; +} + +/* + * Notes on progress track and meter part constants: + * xp and up: + * PP_BAR(_VERT) - base progress track + * PP_TRANSPARENTBAR(_VERT) - transparent progress track. this only works if + * the underlying surface supports alpha. otherwise + * theme lib's DrawThemeBackground falls back on + * opaque PP_BAR. we currently don't use this. + * PP_CHUNK(_VERT) - xp progress meter. this does not draw an xp style + * progress w/chunks, it draws fill using the chunk + * graphic. + * vista and up: + * PP_FILL(_VERT) - progress meter. these have four states/colors. + * PP_PULSEOVERLAY(_VERT) - white reflection - an overlay, not sure what this + * is used for. + * PP_MOVEOVERLAY(_VERT) - green pulse - the pulse effect overlay on + * determined progress bars. we also use this for + * indeterminate chunk. + * + * Notes on state constants: + * PBBS_NORMAL - green progress + * PBBVS_PARTIAL/PBFVS_ERROR - red error progress + * PBFS_PAUSED - yellow paused progress + * + * There is no common controls style indeterminate part on vista and up. + */ + +/* + * Progress bar related constants. These values are found by experimenting and + * comparing against native widgets used by the system. They are very unlikely + * exact but try to not be too wrong. + */ +// The amount of time we animate progress meters parts across the frame. +static const double kProgressDeterminateTimeSpan = 3.0; +static const double kProgressIndeterminateTimeSpan = 5.0; +// The width of the overlay used to animate the horizontal progress bar (Vista and later). +static const int32_t kProgressHorizontalVistaOverlaySize = 120; +// The width of the overlay used for the horizontal indeterminate progress bars on XP. +static const int32_t kProgressHorizontalXPOverlaySize = 55; +// The height of the overlay used to animate the vertical progress bar (Vista and later). +static const int32_t kProgressVerticalOverlaySize = 45; +// The height of the overlay used for the vertical indeterminate progress bar (Vista and later). +static const int32_t kProgressVerticalIndeterminateOverlaySize = 60; +// The width of the overlay used to animate the indeterminate progress bar (Windows Classic). +static const int32_t kProgressClassicOverlaySize = 40; + +/* + * GetProgressOverlayStyle - returns the proper overlay part for themed + * progress bars based on os and orientation. + */ +static int32_t +GetProgressOverlayStyle(bool aIsVertical) +{ + if (aIsVertical) { + if (IsVistaOrLater()) { + return PP_MOVEOVERLAYVERT; + } + return PP_CHUNKVERT; + } else { + if (IsVistaOrLater()) { + return PP_MOVEOVERLAY; + } + return PP_CHUNK; + } +} + +/* + * GetProgressOverlaySize - returns the minimum width or height for themed + * progress bar overlays. This includes the width of indeterminate chunks + * and vista pulse overlays. + */ +static int32_t +GetProgressOverlaySize(bool aIsVertical, bool aIsIndeterminate) +{ + if (IsVistaOrLater()) { + if (aIsVertical) { + return aIsIndeterminate ? kProgressVerticalIndeterminateOverlaySize + : kProgressVerticalOverlaySize; + } + return kProgressHorizontalVistaOverlaySize; + } + return kProgressHorizontalXPOverlaySize; +} + +/* + * IsProgressMeterFilled - Determines if a progress meter is at 100% fill based + * on a comparison of the current value and maximum. + */ +static bool +IsProgressMeterFilled(nsIFrame* aFrame) +{ + NS_ENSURE_TRUE(aFrame, false); + nsIFrame* parentFrame = aFrame->GetParent(); + NS_ENSURE_TRUE(parentFrame, false); + return nsNativeTheme::GetProgressValue(parentFrame) == + nsNativeTheme::GetProgressMaxValue(parentFrame); +} + +/* + * CalculateProgressOverlayRect - returns the padded overlay animation rect + * used in rendering progress bars. Resulting rects are used in rendering + * vista+ pulse overlays and indeterminate progress meters. Graphics should + * be rendered at the origin. + */ +RECT +nsNativeThemeWin::CalculateProgressOverlayRect(nsIFrame* aFrame, + RECT* aWidgetRect, + bool aIsVertical, + bool aIsIndeterminate, + bool aIsClassic) +{ + NS_ASSERTION(aFrame, "bad frame pointer"); + NS_ASSERTION(aWidgetRect, "bad rect pointer"); + + int32_t frameSize = aIsVertical ? aWidgetRect->bottom - aWidgetRect->top + : aWidgetRect->right - aWidgetRect->left; + + // Recycle a set of progress pulse timers - these timers control the position + // of all progress overlays and indeterminate chunks that get rendered. + double span = aIsIndeterminate ? kProgressIndeterminateTimeSpan + : kProgressDeterminateTimeSpan; + TimeDuration period; + if (!aIsIndeterminate) { + if (TimeStamp::Now() > (mProgressDeterminateTimeStamp + + TimeDuration::FromSeconds(span))) { + mProgressDeterminateTimeStamp = TimeStamp::Now(); + } + period = TimeStamp::Now() - mProgressDeterminateTimeStamp; + } else { + if (TimeStamp::Now() > (mProgressIndeterminateTimeStamp + + TimeDuration::FromSeconds(span))) { + mProgressIndeterminateTimeStamp = TimeStamp::Now(); + } + period = TimeStamp::Now() - mProgressIndeterminateTimeStamp; + } + + double percent = period / TimeDuration::FromSeconds(span); + + if (!aIsVertical && IsFrameRTL(aFrame)) + percent = 1 - percent; + + RECT overlayRect = *aWidgetRect; + int32_t overlaySize; + if (!aIsClassic) { + overlaySize = GetProgressOverlaySize(aIsVertical, aIsIndeterminate); + } else { + overlaySize = kProgressClassicOverlaySize; + } + + // Calculate a bounds that is larger than the meters frame such that the + // overlay starts and ends completely off the edge of the frame: + // [overlay][frame][overlay] + // This also yields a nice delay on rotation. Use overlaySize as the minimum + // size for [overlay] based on the graphics dims. If [frame] is larger, use + // the frame size instead. + int trackWidth = frameSize > overlaySize ? frameSize : overlaySize; + if (!aIsVertical) { + int xPos = aWidgetRect->left - trackWidth; + xPos += (int)ceil(((double)(trackWidth*2) * percent)); + overlayRect.left = xPos; + overlayRect.right = xPos + overlaySize; + } else { + int yPos = aWidgetRect->bottom + trackWidth; + yPos -= (int)ceil(((double)(trackWidth*2) * percent)); + overlayRect.bottom = yPos; + overlayRect.top = yPos - overlaySize; + } + return overlayRect; +} + +/* + * DrawChunkProgressMeter - renders an xp style chunked progress meter. Called + * by DrawProgressMeter. + * + * @param aTheme progress theme handle + * @param aHdc hdc returned by gfxWindowsNativeDrawing + * @param aPart the PP_X progress part + * @param aState the theme state + * @param aFrame the elements frame + * @param aWidgetRect bounding rect for the widget + * @param aClipRect dirty rect that needs drawing. + * @param aAppUnits app units per device pixel + * @param aIsIndeterm is an indeterminate progress? + * @param aIsVertical render a vertical progress? + * @param aIsRtl direction is rtl + */ +static void +DrawChunkProgressMeter(HTHEME aTheme, HDC aHdc, int aPart, + int aState, nsIFrame* aFrame, RECT* aWidgetRect, + RECT* aClipRect, gfxFloat aAppUnits, bool aIsIndeterm, + bool aIsVertical, bool aIsRtl) +{ + NS_ASSERTION(aTheme, "Bad theme."); + NS_ASSERTION(aHdc, "Bad hdc."); + NS_ASSERTION(aWidgetRect, "Bad rect."); + NS_ASSERTION(aClipRect, "Bad clip rect."); + NS_ASSERTION(aFrame, "Bad frame."); + + // For horizontal meters, the theme lib paints the right graphic but doesn't + // paint the chunks, so we do that manually. For vertical meters, the theme + // library draws everything correctly. + if (aIsVertical) { + DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect, aClipRect); + return; + } + + // query for the proper chunk metrics + int chunkSize, spaceSize; + if (FAILED(GetThemeMetric(aTheme, aHdc, aPart, aState, + TMT_PROGRESSCHUNKSIZE, &chunkSize)) || + FAILED(GetThemeMetric(aTheme, aHdc, aPart, aState, + TMT_PROGRESSSPACESIZE, &spaceSize))) { + DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect, aClipRect); + return; + } + + // render chunks + if (!aIsRtl || aIsIndeterm) { + for (int chunk = aWidgetRect->left; chunk <= aWidgetRect->right; + chunk += (chunkSize+spaceSize)) { + if (!aIsIndeterm && ((chunk + chunkSize) > aWidgetRect->right)) { + // aWidgetRect->right represents the end of the meter. Partial blocks + // don't get rendered with one exception, so exit here if we don't have + // a full chunk to draw. + // The above is true *except* when the meter is at 100% fill, in which + // case Windows renders any remaining partial block. Query the parent + // frame to find out if we're at 100%. + if (!IsProgressMeterFilled(aFrame)) { + break; + } + } + RECT bounds = + { chunk, aWidgetRect->top, chunk + chunkSize, aWidgetRect->bottom }; + DrawThemeBackground(aTheme, aHdc, aPart, aState, &bounds, aClipRect); + } + } else { + // rtl needs to grow in the opposite direction to look right. + for (int chunk = aWidgetRect->right; chunk >= aWidgetRect->left; + chunk -= (chunkSize+spaceSize)) { + if ((chunk - chunkSize) < aWidgetRect->left) { + if (!IsProgressMeterFilled(aFrame)) { + break; + } + } + RECT bounds = + { chunk - chunkSize, aWidgetRect->top, chunk, aWidgetRect->bottom }; + DrawThemeBackground(aTheme, aHdc, aPart, aState, &bounds, aClipRect); + } + } +} + +/* + * DrawProgressMeter - render an appropriate progress meter based on progress + * meter style, orientation, and os. Note, this does not render the underlying + * progress track. + * + * @param aFrame the widget frame + * @param aWidgetType type of widget + * @param aTheme progress theme handle + * @param aHdc hdc returned by gfxWindowsNativeDrawing + * @param aPart the PP_X progress part + * @param aState the theme state + * @param aWidgetRect bounding rect for the widget + * @param aClipRect dirty rect that needs drawing. + * @param aAppUnits app units per device pixel + */ +void +nsNativeThemeWin::DrawThemedProgressMeter(nsIFrame* aFrame, int aWidgetType, + HANDLE aTheme, HDC aHdc, + int aPart, int aState, + RECT* aWidgetRect, RECT* aClipRect, + gfxFloat aAppUnits) +{ + if (!aFrame || !aTheme || !aHdc) + return; + + NS_ASSERTION(aWidgetRect, "bad rect pointer"); + NS_ASSERTION(aClipRect, "bad clip rect pointer"); + + RECT adjWidgetRect, adjClipRect; + adjWidgetRect = *aWidgetRect; + adjClipRect = *aClipRect; + if (!IsVistaOrLater()) { + // Adjust clipping out by one pixel. XP progress meters are inset, + // Vista+ are not. + InflateRect(&adjWidgetRect, 1, 1); + InflateRect(&adjClipRect, 1, 1); + } + + nsIFrame* parentFrame = aFrame->GetParent(); + if (!parentFrame) { + // We have no parent to work with, just bail. + NS_WARNING("No parent frame for progress rendering. Can't paint."); + return; + } + + EventStates eventStates = GetContentState(parentFrame, aWidgetType); + bool vertical = IsVerticalProgress(parentFrame) || + aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL; + bool indeterminate = IsIndeterminateProgress(parentFrame, eventStates); + bool animate = indeterminate; + + if (IsVistaOrLater()) { + // Vista and up progress meter is fill style, rendered here. We render + // the pulse overlay in the follow up section below. + DrawThemeBackground(aTheme, aHdc, aPart, aState, + &adjWidgetRect, &adjClipRect); + if (!IsProgressMeterFilled(aFrame)) { + animate = true; + } + } else if (!indeterminate) { + // XP progress meters are 'chunk' style. + DrawChunkProgressMeter(aTheme, aHdc, aPart, aState, aFrame, + &adjWidgetRect, &adjClipRect, aAppUnits, + indeterminate, vertical, IsFrameRTL(aFrame)); + } + + if (animate) { + // Indeterminate rendering + int32_t overlayPart = GetProgressOverlayStyle(vertical); + RECT overlayRect = + CalculateProgressOverlayRect(aFrame, &adjWidgetRect, vertical, + indeterminate, false); + if (IsVistaOrLater()) { + DrawThemeBackground(aTheme, aHdc, overlayPart, aState, &overlayRect, + &adjClipRect); + } else { + DrawChunkProgressMeter(aTheme, aHdc, overlayPart, aState, aFrame, + &overlayRect, &adjClipRect, aAppUnits, + indeterminate, vertical, IsFrameRTL(aFrame)); + } + + if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) { + NS_WARNING("unable to animate progress widget!"); + } + } +} + +HANDLE +nsNativeThemeWin::GetTheme(uint8_t aWidgetType) +{ + if (!IsVistaOrLater()) { + // On XP or earlier, render dropdowns as textfields; + // doing it the right way works fine with the MS themes, + // but breaks on a lot of custom themes (presumably because MS + // apps do the textfield border business as well). + if (aWidgetType == NS_THEME_MENULIST) + aWidgetType = NS_THEME_TEXTFIELD; + } + + switch (aWidgetType) { + case NS_THEME_BUTTON: + case NS_THEME_RADIO: + case NS_THEME_CHECKBOX: + case NS_THEME_GROUPBOX: + return nsUXThemeData::GetTheme(eUXButton); + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_FOCUS_OUTLINE: + return nsUXThemeData::GetTheme(eUXEdit); + case NS_THEME_TOOLTIP: + // XP/2K3 should force a classic treatment of tooltips + return !IsVistaOrLater() ? + nullptr : nsUXThemeData::GetTheme(eUXTooltip); + case NS_THEME_TOOLBOX: + return nsUXThemeData::GetTheme(eUXRebar); + case NS_THEME_WIN_MEDIA_TOOLBOX: + return nsUXThemeData::GetTheme(eUXMediaRebar); + case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX: + return nsUXThemeData::GetTheme(eUXCommunicationsRebar); + case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX: + return nsUXThemeData::GetTheme(eUXBrowserTabBarRebar); + case NS_THEME_TOOLBAR: + case NS_THEME_TOOLBARBUTTON: + case NS_THEME_SEPARATOR: + return nsUXThemeData::GetTheme(eUXToolbar); + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + return nsUXThemeData::GetTheme(eUXProgress); + case NS_THEME_TAB: + case NS_THEME_TABPANEL: + case NS_THEME_TABPANELS: + return nsUXThemeData::GetTheme(eUXTab); + case NS_THEME_SCROLLBAR: + case NS_THEME_SCROLLBAR_SMALL: + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + return nsUXThemeData::GetTheme(eUXScrollbar); + case NS_THEME_RANGE: + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: + return nsUXThemeData::GetTheme(eUXTrackbar); + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + return nsUXThemeData::GetTheme(eUXSpin); + case NS_THEME_STATUSBAR: + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_RESIZER: + return nsUXThemeData::GetTheme(eUXStatus); + case NS_THEME_MENULIST: + case NS_THEME_MENULIST_BUTTON: + return nsUXThemeData::GetTheme(eUXCombobox); + case NS_THEME_TREEHEADERCELL: + case NS_THEME_TREEHEADERSORTARROW: + return nsUXThemeData::GetTheme(eUXHeader); + case NS_THEME_LISTBOX: + case NS_THEME_LISTITEM: + case NS_THEME_TREEVIEW: + case NS_THEME_TREETWISTYOPEN: + case NS_THEME_TREEITEM: + return nsUXThemeData::GetTheme(eUXListview); + case NS_THEME_MENUBAR: + case NS_THEME_MENUPOPUP: + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + case NS_THEME_MENUSEPARATOR: + case NS_THEME_MENUARROW: + case NS_THEME_MENUIMAGE: + case NS_THEME_MENUITEMTEXT: + return nsUXThemeData::GetTheme(eUXMenu); + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + case NS_THEME_WINDOW_FRAME_BOTTOM: + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + case NS_THEME_WIN_GLASS: + case NS_THEME_WIN_BORDERLESS_GLASS: + return nsUXThemeData::GetTheme(eUXWindowFrame); + } + return nullptr; +} + +int32_t +nsNativeThemeWin::StandardGetState(nsIFrame* aFrame, uint8_t aWidgetType, + bool wantFocused) +{ + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + return TS_ACTIVE; + if (eventState.HasState(NS_EVENT_STATE_HOVER)) + return TS_HOVER; + if (wantFocused && eventState.HasState(NS_EVENT_STATE_FOCUS)) + return TS_FOCUSED; + + return TS_NORMAL; +} + +bool +nsNativeThemeWin::IsMenuActive(nsIFrame *aFrame, uint8_t aWidgetType) +{ + nsIContent* content = aFrame->GetContent(); + if (content->IsXULElement() && + content->NodeInfo()->Equals(nsGkAtoms::richlistitem)) + return CheckBooleanAttr(aFrame, nsGkAtoms::selected); + + return CheckBooleanAttr(aFrame, nsGkAtoms::menuactive); +} + +/** + * aPart is filled in with the UXTheme part code. On return, values > 0 + * are the actual UXTheme part code; -1 means the widget will be drawn by + * us; 0 means that we should use part code 0, which isn't a real part code + * but elicits some kind of default behaviour from UXTheme when drawing + * (but isThemeBackgroundPartiallyTransparent may not work). + */ +nsresult +nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType, + int32_t& aPart, int32_t& aState) +{ + if (!IsVistaOrLater()) { + // See GetTheme + if (aWidgetType == NS_THEME_MENULIST) + aWidgetType = NS_THEME_TEXTFIELD; + } + + switch (aWidgetType) { + case NS_THEME_BUTTON: { + aPart = BP_BUTTON; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + return NS_OK; + } else if (IsOpenButton(aFrame) || + IsCheckedButton(aFrame)) { + aState = TS_ACTIVE; + return NS_OK; + } + + aState = StandardGetState(aFrame, aWidgetType, true); + + // Check for default dialog buttons. These buttons should always look + // focused. + if (aState == TS_NORMAL && IsDefaultButton(aFrame)) + aState = TS_FOCUSED; + return NS_OK; + } + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: { + bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX); + aPart = isCheckbox ? BP_CHECKBOX : BP_RADIO; + + enum InputState { + UNCHECKED = 0, CHECKED, INDETERMINATE + }; + InputState inputState = UNCHECKED; + bool isXULCheckboxRadio = false; + + if (!aFrame) { + aState = TS_NORMAL; + } else { + if (GetCheckedOrSelected(aFrame, !isCheckbox)) { + inputState = CHECKED; + } if (isCheckbox && GetIndeterminate(aFrame)) { + inputState = INDETERMINATE; + } + + EventStates eventState = + GetContentState(isXULCheckboxRadio ? aFrame->GetParent() : aFrame, + aWidgetType); + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + } else { + aState = StandardGetState(aFrame, aWidgetType, false); + } + } + + // 4 unchecked states, 4 checked states, 4 indeterminate states. + aState += inputState * 4; + return NS_OK; + } + case NS_THEME_GROUPBOX: { + aPart = BP_GROUPBOX; + aState = TS_NORMAL; + // Since we don't support groupbox disabled and GBS_DISABLED looks the + // same as GBS_NORMAL don't bother supporting GBS_DISABLED. + return NS_OK; + } + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: { + EventStates eventState = GetContentState(aFrame, aWidgetType); + + if (IsVistaOrLater()) { + /* Note: the NOSCROLL type has a rounded corner in each + * corner. The more specific HSCROLL, VSCROLL, HVSCROLL types + * have side and/or top/bottom edges rendered as straight + * horizontal lines with sharp corners to accommodate a + * scrollbar. However, the scrollbar gets rendered on top of + * this for us, so we don't care, and can just use NOSCROLL + * here. + */ + aPart = TFP_EDITBORDER_NOSCROLL; + + if (!aFrame) { + aState = TFS_EDITBORDER_NORMAL; + } else if (IsDisabled(aFrame, eventState)) { + aState = TFS_EDITBORDER_DISABLED; + } else if (IsReadOnly(aFrame)) { + /* no special read-only state */ + aState = TFS_EDITBORDER_NORMAL; + } else { + nsIContent* content = aFrame->GetContent(); + + /* XUL textboxes don't get focused themselves, because they have child + * html:input.. but we can check the XUL focused attributes on them + */ + if (content && content->IsXULElement() && IsFocused(aFrame)) + aState = TFS_EDITBORDER_FOCUSED; + else if (eventState.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS)) + aState = TFS_EDITBORDER_FOCUSED; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState = TFS_EDITBORDER_HOVER; + else + aState = TFS_EDITBORDER_NORMAL; + } + } else { + aPart = TFP_TEXTFIELD; + + if (!aFrame) + aState = TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) + aState = TS_DISABLED; + else if (IsReadOnly(aFrame)) + aState = TFS_READONLY; + else + aState = StandardGetState(aFrame, aWidgetType, true); + } + + return NS_OK; + } + case NS_THEME_FOCUS_OUTLINE: { + if (IsVistaOrLater()) { + // XXX the EDITBORDER values don't respect DTBG_OMITCONTENT + aPart = TFP_TEXTFIELD; //TFP_EDITBORDER_NOSCROLL; + aState = TS_FOCUSED; //TFS_EDITBORDER_FOCUSED; + } else { + aPart = TFP_TEXTFIELD; + aState = TS_FOCUSED; + } + return NS_OK; + } + case NS_THEME_TOOLTIP: { + aPart = TTP_STANDARD; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: { + // Note IsVerticalProgress only tests for orient css attrribute, + // NS_THEME_PROGRESSBAR_VERTICAL is dedicated to -moz-appearance: + // progressbar-vertical. + bool vertical = IsVerticalProgress(aFrame) || + aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL; + aPart = vertical ? PP_BARVERT : PP_BAR; + aState = PBBS_NORMAL; + return NS_OK; + } + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: { + nsIFrame* parentFrame = aFrame->GetParent(); + if (aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL || + IsVerticalProgress(parentFrame)) { + aPart = IsVistaOrLater() ? + PP_FILLVERT : PP_CHUNKVERT; + } else { + aPart = IsVistaOrLater() ? + PP_FILL : PP_CHUNK; + } + + aState = PBBVS_NORMAL; + return NS_OK; + } + case NS_THEME_TOOLBARBUTTON: { + aPart = BP_BUTTON; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + return NS_OK; + } + if (IsOpenButton(aFrame)) { + aState = TS_ACTIVE; + return NS_OK; + } + + if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + aState = TS_ACTIVE; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) { + if (IsCheckedButton(aFrame)) + aState = TB_HOVER_CHECKED; + else + aState = TS_HOVER; + } + else { + if (IsCheckedButton(aFrame)) + aState = TB_CHECKED; + else + aState = TS_NORMAL; + } + + return NS_OK; + } + case NS_THEME_SEPARATOR: { + aPart = TP_SEPARATOR; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: { + aPart = SP_BUTTON; + aState = (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP)*4; + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (!aFrame) + aState += TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) + aState += TS_DISABLED; + else { + nsIFrame *parent = aFrame->GetParent(); + EventStates parentState = + GetContentState(parent, parent->StyleDisplay()->mAppearance); + if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + aState += TS_ACTIVE; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState += TS_HOVER; + else if (IsVistaOrLater() && + parentState.HasState(NS_EVENT_STATE_HOVER)) + aState = (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP) + SP_BUTTON_IMPLICIT_HOVER_BASE; + else + aState += TS_NORMAL; + } + return NS_OK; + } + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: { + aPart = (aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL) ? + SP_TRACKSTARTHOR : SP_TRACKSTARTVERT; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case NS_THEME_SCROLLBARTHUMB_VERTICAL: { + aPart = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) ? + SP_THUMBHOR : SP_THUMBVERT; + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (!aFrame) + aState = TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) + aState = TS_DISABLED; + else { + if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) // Hover is not also a requirement for + // the thumb, since the drag is not canceled + // when you move outside the thumb. + aState = TS_ACTIVE; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState = TS_HOVER; + else + aState = TS_NORMAL; + } + return NS_OK; + } + case NS_THEME_RANGE: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: { + if (aWidgetType == NS_THEME_SCALE_HORIZONTAL || + (aWidgetType == NS_THEME_RANGE && + IsRangeHorizontal(aFrame))) { + aPart = TKP_TRACK; + aState = TRS_NORMAL; + } else { + aPart = TKP_TRACKVERT; + aState = TRVS_NORMAL; + } + return NS_OK; + } + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: { + if (aWidgetType == NS_THEME_RANGE_THUMB) { + if (IsRangeHorizontal(aFrame)) { + aPart = TKP_THUMBBOTTOM; + } else { + aPart = IsFrameRTL(aFrame) ? TKP_THUMBLEFT : TKP_THUMBRIGHT; + } + } else { + aPart = (aWidgetType == NS_THEME_SCALETHUMB_HORIZONTAL) ? + TKP_THUMB : TKP_THUMBVERT; + } + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (!aFrame) + aState = TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) { + aState = TKP_DISABLED; + } + else { + if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) // Hover is not also a requirement for + // the thumb, since the drag is not canceled + // when you move outside the thumb. + aState = TS_ACTIVE; + else if (eventState.HasState(NS_EVENT_STATE_FOCUS)) + aState = TKP_FOCUSED; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState = TS_HOVER; + else + aState = TS_NORMAL; + } + return NS_OK; + } + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: { + aPart = (aWidgetType == NS_THEME_SPINNER_UPBUTTON) ? + SPNP_UP : SPNP_DOWN; + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (!aFrame) + aState = TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) + aState = TS_DISABLED; + else + aState = StandardGetState(aFrame, aWidgetType, false); + return NS_OK; + } + case NS_THEME_TOOLBOX: + case NS_THEME_WIN_MEDIA_TOOLBOX: + case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX: + case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX: + case NS_THEME_STATUSBAR: + case NS_THEME_SCROLLBAR: + case NS_THEME_SCROLLBAR_SMALL: { + aState = 0; + if (IsVistaOrLater()) { + // On vista, they have a part + aPart = RP_BACKGROUND; + } else { + // Otherwise, they don't. (But I bet + // RP_BACKGROUND would work here, too); + aPart = 0; + } + return NS_OK; + } + case NS_THEME_TOOLBAR: { + // Use -1 to indicate we don't wish to have the theme background drawn + // for this item. We will pass any nessessary information via aState, + // and will render the item using separate code. + aPart = -1; + aState = 0; + if (aFrame) { + nsIContent* content = aFrame->GetContent(); + nsIContent* parent = content->GetParent(); + // XXXzeniko hiding the first toolbar will result in an unwanted margin + if (parent && parent->GetFirstChild() == content) { + aState = 1; + } + } + return NS_OK; + } + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_RESIZER: { + aPart = (aWidgetType - NS_THEME_STATUSBARPANEL) + 1; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_TREEVIEW: + case NS_THEME_LISTBOX: { + aPart = TREEVIEW_BODY; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_TABPANELS: { + aPart = TABP_PANELS; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_TABPANEL: { + aPart = TABP_PANEL; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_TAB: { + aPart = TABP_TAB; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + return NS_OK; + } + + if (IsSelectedTab(aFrame)) { + aPart = TABP_TAB_SELECTED; + aState = TS_ACTIVE; // The selected tab is always "pressed". + } + else + aState = StandardGetState(aFrame, aWidgetType, true); + + return NS_OK; + } + case NS_THEME_TREEHEADERSORTARROW: { + // XXX Probably will never work due to a bug in the Luna theme. + aPart = 4; + aState = 1; + return NS_OK; + } + case NS_THEME_TREEHEADERCELL: { + aPart = 1; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + aState = StandardGetState(aFrame, aWidgetType, true); + + return NS_OK; + } + case NS_THEME_MENULIST: { + nsIContent* content = aFrame->GetContent(); + bool isHTML = content && content->IsHTMLElement(); + bool useDropBorder = isHTML || IsMenuListEditable(aFrame); + EventStates eventState = GetContentState(aFrame, aWidgetType); + + /* On Vista/Win7, we use CBP_DROPBORDER instead of DROPFRAME for HTML + * content or for editable menulists; this gives us the thin outline, + * instead of the gradient-filled background */ + if (useDropBorder) + aPart = CBP_DROPBORDER; + else + aPart = CBP_DROPFRAME; + + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + } else if (IsReadOnly(aFrame)) { + aState = TS_NORMAL; + } else if (IsOpenButton(aFrame)) { + aState = TS_ACTIVE; + } else { + if (useDropBorder && (eventState.HasState(NS_EVENT_STATE_FOCUS) || IsFocused(aFrame))) + aState = TS_ACTIVE; + else if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + aState = TS_ACTIVE; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState = TS_HOVER; + else + aState = TS_NORMAL; + } + + return NS_OK; + } + case NS_THEME_MENULIST_BUTTON: { + bool isHTML = IsHTMLContent(aFrame); + nsIFrame* parentFrame = aFrame->GetParent(); + bool isMenulist = !isHTML && parentFrame->GetType() == nsGkAtoms::menuFrame; + bool isOpen = false; + + // HTML select and XUL menulist dropdown buttons get state from the parent. + if (isHTML || isMenulist) + aFrame = parentFrame; + + EventStates eventState = GetContentState(aFrame, aWidgetType); + aPart = IsVistaOrLater() ? + CBP_DROPMARKER_VISTA : CBP_DROPMARKER; + + // For HTML controls with author styling, we should fall + // back to the old dropmarker style to avoid clashes with + // author-specified backgrounds and borders (bug #441034) + if (isHTML && IsWidgetStyled(aFrame->PresContext(), aFrame, NS_THEME_MENULIST)) + aPart = CBP_DROPMARKER; + + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + return NS_OK; + } + + if (isHTML) { + nsIComboboxControlFrame* ccf = do_QueryFrame(aFrame); + isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup()); + } + else + isOpen = IsOpenButton(aFrame); + + if (IsVistaOrLater()) { + if (isHTML || IsMenuListEditable(aFrame)) { + if (isOpen) { + /* Hover is propagated, but we need to know whether we're + * hovering just the combobox frame, not the dropdown frame. + * But, we can't get that information, since hover is on the + * content node, and they share the same content node. So, + * instead, we cheat -- if the dropdown is open, we always + * show the hover state. This looks fine in practice. + */ + aState = TS_HOVER; + return NS_OK; + } + } else { + /* On Vista, the dropdown indicator on a menulist button in + * chrome is not given a hover effect. When the frame isn't + * isn't HTML content, we cheat and force the dropdown state + * to be normal. (Bug 430434) + */ + aState = TS_NORMAL; + return NS_OK; + } + } + + aState = TS_NORMAL; + + // Dropdown button active state doesn't need :hover. + if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) { + if (isOpen && (isHTML || isMenulist)) { + // XXX Button should look active until the mouse is released, but + // without making it look active when the popup is clicked. + return NS_OK; + } + aState = TS_ACTIVE; + } + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) { + // No hover effect for XUL menulists and autocomplete dropdown buttons + // while the dropdown menu is open. + if (isOpen) { + // XXX HTML select dropdown buttons should have the hover effect when + // hovering the combobox frame, but not the popup frame. + return NS_OK; + } + aState = TS_HOVER; + } + return NS_OK; + } + case NS_THEME_MENUPOPUP: { + aPart = MENU_POPUPBACKGROUND; + aState = MB_ACTIVE; + return NS_OK; + } + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: { + bool isTopLevel = false; + bool isOpen = false; + bool isHover = false; + nsMenuFrame *menuFrame = do_QueryFrame(aFrame); + EventStates eventState = GetContentState(aFrame, aWidgetType); + + isTopLevel = IsTopLevelMenu(aFrame); + + if (menuFrame) + isOpen = menuFrame->IsOpen(); + + isHover = IsMenuActive(aFrame, aWidgetType); + + if (isTopLevel) { + aPart = MENU_BARITEM; + + if (isOpen) + aState = MBI_PUSHED; + else if (isHover) + aState = MBI_HOT; + else + aState = MBI_NORMAL; + + // the disabled states are offset by 3 + if (IsDisabled(aFrame, eventState)) + aState += 3; + } else { + aPart = MENU_POPUPITEM; + + if (isHover) + aState = MPI_HOT; + else + aState = MPI_NORMAL; + + // the disabled states are offset by 2 + if (IsDisabled(aFrame, eventState)) + aState += 2; + } + + return NS_OK; + } + case NS_THEME_MENUSEPARATOR: + aPart = MENU_POPUPSEPARATOR; + aState = 0; + return NS_OK; + case NS_THEME_MENUARROW: + { + aPart = MENU_POPUPSUBMENU; + EventStates eventState = GetContentState(aFrame, aWidgetType); + aState = IsDisabled(aFrame, eventState) ? MSM_DISABLED : MSM_NORMAL; + return NS_OK; + } + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + { + EventStates eventState = GetContentState(aFrame, aWidgetType); + + aPart = MENU_POPUPCHECK; + aState = MC_CHECKMARKNORMAL; + + // Radio states are offset by 2 + if (aWidgetType == NS_THEME_MENURADIO) + aState += 2; + + // the disabled states are offset by 1 + if (IsDisabled(aFrame, eventState)) + aState += 1; + + return NS_OK; + } + case NS_THEME_MENUITEMTEXT: + case NS_THEME_MENUIMAGE: + aPart = -1; + aState = 0; + return NS_OK; + + case NS_THEME_WINDOW_TITLEBAR: + aPart = mozilla::widget::themeconst::WP_CAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + aPart = mozilla::widget::themeconst::WP_MAXCAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_LEFT: + aPart = mozilla::widget::themeconst::WP_FRAMELEFT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_RIGHT: + aPart = mozilla::widget::themeconst::WP_FRAMERIGHT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_BOTTOM: + aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_CLOSE: + aPart = mozilla::widget::themeconst::WP_CLOSEBUTTON; + aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + aPart = mozilla::widget::themeconst::WP_MINBUTTON; + aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + aPart = mozilla::widget::themeconst::WP_MAXBUTTON; + aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_RESTORE: + aPart = mozilla::widget::themeconst::WP_RESTOREBUTTON; + aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + case NS_THEME_WIN_GLASS: + case NS_THEME_WIN_BORDERLESS_GLASS: + aPart = -1; + aState = 0; + return NS_OK; + } + + aPart = 0; + aState = 0; + return NS_ERROR_FAILURE; +} + +static bool +AssumeThemePartAndStateAreTransparent(int32_t aPart, int32_t aState) +{ + if (aPart == MENU_POPUPITEM && aState == MBI_NORMAL) { + return true; + } + return false; +} + +// When running with per-monitor DPI (on Win8.1+), and rendering on a display +// with a different DPI setting from the system's default scaling, we need to +// apply scaling to native-themed elements as the Windows theme APIs assume +// the system default resolution. +static inline double +GetThemeDpiScaleFactor(nsIFrame* aFrame) +{ + if (WinUtils::IsPerMonitorDPIAware()) { + nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget(); + if (rootWidget) { + double systemScale = WinUtils::SystemScaleFactor(); + return rootWidget->GetDefaultScale().scale / systemScale; + } + } + return 1.0; +} + +NS_IMETHODIMP +nsNativeThemeWin::DrawWidgetBackground(nsRenderingContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + const nsRect& aRect, + const nsRect& aDirtyRect) +{ + HANDLE theme = GetTheme(aWidgetType); + if (!theme) + return ClassicDrawWidgetBackground(aContext, aFrame, aWidgetType, aRect, aDirtyRect); + + // ^^ without the right sdk, assume xp theming and fall through. + if (nsUXThemeData::CheckForCompositor()) { + switch (aWidgetType) { + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + case NS_THEME_WINDOW_FRAME_BOTTOM: + // Nothing to draw, these areas are glass. Minimum dimensions + // should be set, so xul content should be layed out correctly. + return NS_OK; + break; + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + // Not conventional bitmaps, can't be retrieved. If we fall + // through here and call the theme library we'll get aero + // basic bitmaps. + return NS_OK; + break; + case NS_THEME_WIN_GLASS: + case NS_THEME_WIN_BORDERLESS_GLASS: + // Nothing to draw, this is the glass background. + return NS_OK; + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + // We handle these through nsIWidget::UpdateThemeGeometries + return NS_OK; + break; + } + } + + int32_t part, state; + nsresult rv = GetThemePartAndState(aFrame, aWidgetType, part, state); + if (NS_FAILED(rv)) + return rv; + + if (AssumeThemePartAndStateAreTransparent(part, state)) { + return NS_OK; + } + + RefPtr<gfxContext> ctx = aContext->ThebesContext(); + gfxContextMatrixAutoSaveRestore save(ctx); + + double themeScale = GetThemeDpiScaleFactor(aFrame); + if (themeScale != 1.0) { + ctx->SetMatrix(ctx->CurrentMatrix().Scale(themeScale, themeScale)); + } + + gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel()); + RECT widgetRect; + RECT clipRect; + gfxRect tr(aRect.x, aRect.y, aRect.width, aRect.height), + dr(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height); + + tr.ScaleInverse(p2a * themeScale); + dr.ScaleInverse(p2a * themeScale); + + gfxWindowsNativeDrawing nativeDrawing(ctx, dr, GetWidgetNativeDrawingFlags(aWidgetType)); + +RENDER_AGAIN: + + HDC hdc = nativeDrawing.BeginNativeDrawing(); + if (!hdc) + return NS_ERROR_FAILURE; + + nativeDrawing.TransformToNativeRect(tr, widgetRect); + nativeDrawing.TransformToNativeRect(dr, clipRect); + +#if 0 + { + MOZ_LOG(gWindowsLog, LogLevel::Error, + (stderr, "xform: %f %f %f %f [%f %f]\n", m._11, m._21, m._12, m._22, + m._31, m._32)); + MOZ_LOG(gWindowsLog, LogLevel::Error, + (stderr, "tr: [%d %d %d %d]\ndr: [%d %d %d %d]\noff: [%f %f]\n", + tr.x, tr.y, tr.width, tr.height, dr.x, dr.y, dr.width, dr.height, + offset.x, offset.y)); + } +#endif + + if (aWidgetType == NS_THEME_WINDOW_TITLEBAR) { + // Clip out the left and right corners of the frame, all we want in + // is the middle section. + widgetRect.left -= GetSystemMetrics(SM_CXFRAME); + widgetRect.right += GetSystemMetrics(SM_CXFRAME); + } else if (aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) { + // The origin of the window is off screen when maximized and windows + // doesn't compensate for this in rendering the background. Push the + // top of the bitmap down by SM_CYFRAME so we get the full graphic. + widgetRect.top += GetSystemMetrics(SM_CYFRAME); + } else if (aWidgetType == NS_THEME_TAB) { + // For left edge and right edge tabs, we need to adjust the widget + // rects and clip rects so that the edges don't get drawn. + bool isLeft = IsLeftToSelectedTab(aFrame); + bool isRight = !isLeft && IsRightToSelectedTab(aFrame); + + if (isLeft || isRight) { + // HACK ALERT: There appears to be no way to really obtain this value, so we're forced + // to just use the default value for Luna (which also happens to be correct for + // all the other skins I've tried). + int32_t edgeSize = 2; + + // Armed with the size of the edge, we now need to either shift to the left or to the + // right. The clip rect won't include this extra area, so we know that we're + // effectively shifting the edge out of view (such that it won't be painted). + if (isLeft) + // The right edge should not be drawn. Extend our rect by the edge size. + widgetRect.right += edgeSize; + else + // The left edge should not be drawn. Move the widget rect's left coord back. + widgetRect.left -= edgeSize; + } + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE || + aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE); + } + + // widgetRect is the bounding box for a widget, yet the scale track is only + // a small portion of this size, so the edges of the scale need to be + // adjusted to the real size of the track. + if (aWidgetType == NS_THEME_RANGE || + aWidgetType == NS_THEME_SCALE_HORIZONTAL || + aWidgetType == NS_THEME_SCALE_VERTICAL) { + RECT contentRect; + GetThemeBackgroundContentRect(theme, hdc, part, state, &widgetRect, &contentRect); + + SIZE siz; + GetThemePartSize(theme, hdc, part, state, &widgetRect, TS_TRUE, &siz); + + // When rounding is necessary, we round the position of the track + // away from the chevron of the thumb to make it look better. + if (aWidgetType == NS_THEME_SCALE_HORIZONTAL || + (aWidgetType == NS_THEME_RANGE && IsRangeHorizontal(aFrame))) { + contentRect.top += (contentRect.bottom - contentRect.top - siz.cy) / 2; + contentRect.bottom = contentRect.top + siz.cy; + } + else { + if (!IsFrameRTL(aFrame)) { + contentRect.left += (contentRect.right - contentRect.left - siz.cx) / 2; + contentRect.right = contentRect.left + siz.cx; + } else { + contentRect.right -= (contentRect.right - contentRect.left - siz.cx) / 2; + contentRect.left = contentRect.right - siz.cx; + } + } + + DrawThemeBackground(theme, hdc, part, state, &contentRect, &clipRect); + } + else if (aWidgetType == NS_THEME_MENUCHECKBOX || aWidgetType == NS_THEME_MENURADIO) + { + bool isChecked = false; + isChecked = CheckBooleanAttr(aFrame, nsGkAtoms::checked); + + if (isChecked) + { + int bgState = MCB_NORMAL; + EventStates eventState = GetContentState(aFrame, aWidgetType); + + // the disabled states are offset by 1 + if (IsDisabled(aFrame, eventState)) + bgState += 1; + + SIZE checkboxBGSize(GetCheckboxBGSize(theme, hdc)); + + RECT checkBGRect = widgetRect; + if (IsFrameRTL(aFrame)) { + checkBGRect.left = checkBGRect.right-checkboxBGSize.cx; + } else { + checkBGRect.right = checkBGRect.left+checkboxBGSize.cx; + } + + // Center the checkbox background vertically in the menuitem + checkBGRect.top += (checkBGRect.bottom - checkBGRect.top)/2 - checkboxBGSize.cy/2; + checkBGRect.bottom = checkBGRect.top + checkboxBGSize.cy; + + DrawThemeBackground(theme, hdc, MENU_POPUPCHECKBACKGROUND, bgState, &checkBGRect, &clipRect); + + MARGINS checkMargins = GetCheckboxMargins(theme, hdc); + RECT checkRect = checkBGRect; + checkRect.left += checkMargins.cxLeftWidth; + checkRect.right -= checkMargins.cxRightWidth; + checkRect.top += checkMargins.cyTopHeight; + checkRect.bottom -= checkMargins.cyBottomHeight; + DrawThemeBackground(theme, hdc, MENU_POPUPCHECK, state, &checkRect, &clipRect); + } + } + else if (aWidgetType == NS_THEME_MENUPOPUP) + { + DrawThemeBackground(theme, hdc, MENU_POPUPBORDERS, /* state */ 0, &widgetRect, &clipRect); + SIZE borderSize; + GetThemePartSize(theme, hdc, MENU_POPUPBORDERS, 0, nullptr, TS_TRUE, &borderSize); + + RECT bgRect = widgetRect; + bgRect.top += borderSize.cy; + bgRect.bottom -= borderSize.cy; + bgRect.left += borderSize.cx; + bgRect.right -= borderSize.cx; + + DrawThemeBackground(theme, hdc, MENU_POPUPBACKGROUND, /* state */ 0, &bgRect, &clipRect); + + SIZE gutterSize(GetGutterSize(theme, hdc)); + + RECT gutterRect; + gutterRect.top = bgRect.top; + gutterRect.bottom = bgRect.bottom; + if (IsFrameRTL(aFrame)) { + gutterRect.right = bgRect.right; + gutterRect.left = gutterRect.right-gutterSize.cx; + } else { + gutterRect.left = bgRect.left; + gutterRect.right = gutterRect.left+gutterSize.cx; + } + + DrawThemeBGRTLAware(theme, hdc, MENU_POPUPGUTTER, /* state */ 0, + &gutterRect, &clipRect, IsFrameRTL(aFrame)); + } + else if (aWidgetType == NS_THEME_MENUSEPARATOR) + { + SIZE gutterSize(GetGutterSize(theme,hdc)); + + RECT sepRect = widgetRect; + if (IsFrameRTL(aFrame)) + sepRect.right -= gutterSize.cx; + else + sepRect.left += gutterSize.cx; + + DrawThemeBackground(theme, hdc, MENU_POPUPSEPARATOR, /* state */ 0, &sepRect, &clipRect); + } + else if (aWidgetType == NS_THEME_MENUARROW) + { + // We're dpi aware and as such on systems that have dpi > 96 set, the + // theme library expects us to do proper positioning and scaling of glyphs. + // For NS_THEME_MENUARROW, layout may hand us a widget rect larger than the + // glyph rect we request in GetMinimumWidgetSize. To prevent distortion we + // have to position and scale what we draw. + + SIZE glyphSize; + GetThemePartSize(theme, hdc, part, state, nullptr, TS_TRUE, &glyphSize); + + int32_t widgetHeight = widgetRect.bottom - widgetRect.top; + + RECT renderRect = widgetRect; + + // We request (glyph width * 2, glyph height) in GetMinimumWidgetSize. In + // Firefox some menu items provide the full height of the item to us, in + // others our widget rect is the exact dims of our arrow glyph. Adjust the + // vertical position by the added space, if any exists. + renderRect.top += ((widgetHeight - glyphSize.cy) / 2); + renderRect.bottom = renderRect.top + glyphSize.cy; + // I'm using the width of the arrow glyph for the arrow-side padding. + // AFAICT there doesn't appear to be a theme constant we can query + // for this value. Generally this looks correct, and has the added + // benefit of being a dpi adjusted value. + if (!IsFrameRTL(aFrame)) { + renderRect.right = widgetRect.right - glyphSize.cx; + renderRect.left = renderRect.right - glyphSize.cx; + } else { + renderRect.left = glyphSize.cx; + renderRect.right = renderRect.left + glyphSize.cx; + } + DrawThemeBGRTLAware(theme, hdc, part, state, &renderRect, &clipRect, + IsFrameRTL(aFrame)); + } + // The following widgets need to be RTL-aware + else if (aWidgetType == NS_THEME_RESIZER || + aWidgetType == NS_THEME_MENULIST_BUTTON) + { + DrawThemeBGRTLAware(theme, hdc, part, state, + &widgetRect, &clipRect, IsFrameRTL(aFrame)); + } + else if (aWidgetType == NS_THEME_NUMBER_INPUT || + aWidgetType == NS_THEME_TEXTFIELD || + aWidgetType == NS_THEME_TEXTFIELD_MULTILINE) { + DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect); + if (state == TFS_EDITBORDER_DISABLED) { + InflateRect(&widgetRect, -1, -1); + ::FillRect(hdc, &widgetRect, reinterpret_cast<HBRUSH>(COLOR_BTNFACE+1)); + } + } + else if (aWidgetType == NS_THEME_PROGRESSBAR || + aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL) { + // DrawThemeBackground renders each corner with a solid white pixel. + // Restore these pixels to the underlying color. Tracks are rendered + // using alpha recovery, so this makes the corners transparent. + COLORREF color; + color = GetPixel(hdc, widgetRect.left, widgetRect.top); + DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect); + SetPixel(hdc, widgetRect.left, widgetRect.top, color); + SetPixel(hdc, widgetRect.right-1, widgetRect.top, color); + SetPixel(hdc, widgetRect.right-1, widgetRect.bottom-1, color); + SetPixel(hdc, widgetRect.left, widgetRect.bottom-1, color); + } + else if (aWidgetType == NS_THEME_PROGRESSCHUNK || + aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL) { + DrawThemedProgressMeter(aFrame, aWidgetType, theme, hdc, part, state, + &widgetRect, &clipRect, p2a); + } + else if (aWidgetType == NS_THEME_FOCUS_OUTLINE) { + // Inflate 'widgetRect' with the focus outline size. + nsIntMargin border; + if (NS_SUCCEEDED(GetWidgetBorder(aFrame->PresContext()->DeviceContext(), + aFrame, aWidgetType, &border))) { + widgetRect.left -= border.left; + widgetRect.right += border.right; + widgetRect.top -= border.top; + widgetRect.bottom += border.bottom; + } + + DTBGOPTS opts = { + sizeof(DTBGOPTS), + DTBG_OMITCONTENT | DTBG_CLIPRECT, + clipRect + }; + DrawThemeBackgroundEx(theme, hdc, part, state, &widgetRect, &opts); + } + // If part is negative, the element wishes us to not render a themed + // background, instead opting to be drawn specially below. + else if (part >= 0) { + DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect); + } + + // Draw focus rectangles for range and scale elements + // XXX it'd be nice to draw these outside of the frame + if (aWidgetType == NS_THEME_RANGE || + aWidgetType == NS_THEME_SCALE_HORIZONTAL || + aWidgetType == NS_THEME_SCALE_VERTICAL) { + EventStates contentState = GetContentState(aFrame, aWidgetType); + + if (contentState.HasState(NS_EVENT_STATE_FOCUS)) { + POINT vpOrg; + HPEN hPen = nullptr; + + uint8_t id = SaveDC(hdc); + + ::SelectClipRgn(hdc, nullptr); + ::GetViewportOrgEx(hdc, &vpOrg); + ::SetBrushOrgEx(hdc, vpOrg.x + widgetRect.left, vpOrg.y + widgetRect.top, nullptr); + ::SetTextColor(hdc, 0); + ::DrawFocusRect(hdc, &widgetRect); + ::RestoreDC(hdc, id); + if (hPen) { + ::DeleteObject(hPen); + } + } + } + else if (aWidgetType == NS_THEME_TOOLBAR && state == 0) { + // Draw toolbar separator lines above all toolbars except the first one. + // The lines are part of the Rebar theme, which is loaded for NS_THEME_TOOLBOX. + theme = GetTheme(NS_THEME_TOOLBOX); + if (!theme) + return NS_ERROR_FAILURE; + + widgetRect.bottom = widgetRect.top + TB_SEPARATOR_HEIGHT; + DrawThemeEdge(theme, hdc, RP_BAND, 0, &widgetRect, EDGE_ETCHED, BF_TOP, nullptr); + } + else if (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL || + aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL) + { + // Draw the decorative gripper for the scrollbar thumb button, if it fits + + SIZE gripSize; + MARGINS thumbMgns; + int gripPart = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) ? + SP_GRIPPERHOR : SP_GRIPPERVERT; + + if (GetThemePartSize(theme, hdc, gripPart, state, nullptr, TS_TRUE, &gripSize) == S_OK && + GetThemeMargins(theme, hdc, part, state, TMT_CONTENTMARGINS, nullptr, &thumbMgns) == S_OK && + gripSize.cx + thumbMgns.cxLeftWidth + thumbMgns.cxRightWidth <= widgetRect.right - widgetRect.left && + gripSize.cy + thumbMgns.cyTopHeight + thumbMgns.cyBottomHeight <= widgetRect.bottom - widgetRect.top) + { + DrawThemeBackground(theme, hdc, gripPart, state, &widgetRect, &clipRect); + } + } + + nativeDrawing.EndNativeDrawing(); + + if (nativeDrawing.ShouldRenderAgain()) + goto RENDER_AGAIN; + + nativeDrawing.PaintToContext(); + + return NS_OK; +} + +static void +ScaleForFrameDPI(nsIntMargin* aMargin, nsIFrame* aFrame) +{ + double themeScale = GetThemeDpiScaleFactor(aFrame); + if (themeScale != 1.0) { + aMargin->top = NSToIntRound(aMargin->top * themeScale); + aMargin->left = NSToIntRound(aMargin->left * themeScale); + aMargin->bottom = NSToIntRound(aMargin->bottom * themeScale); + aMargin->right = NSToIntRound(aMargin->right * themeScale); + } +} + +static void +ScaleForFrameDPI(LayoutDeviceIntSize* aSize, nsIFrame* aFrame) +{ + double themeScale = GetThemeDpiScaleFactor(aFrame); + if (themeScale != 1.0) { + aSize->width = NSToIntRound(aSize->width * themeScale); + aSize->height = NSToIntRound(aSize->height * themeScale); + } +} + +NS_IMETHODIMP +nsNativeThemeWin::GetWidgetBorder(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) +{ + HANDLE theme = GetTheme(aWidgetType); + nsresult rv = NS_OK; + if (!theme) { + rv = ClassicGetWidgetBorder(aContext, aFrame, aWidgetType, aResult); + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + + aResult->top = aResult->bottom = aResult->left = aResult->right = 0; + + if (!WidgetIsContainer(aWidgetType) || + aWidgetType == NS_THEME_TOOLBOX || + aWidgetType == NS_THEME_WIN_MEDIA_TOOLBOX || + aWidgetType == NS_THEME_WIN_COMMUNICATIONS_TOOLBOX || + aWidgetType == NS_THEME_WIN_BROWSERTABBAR_TOOLBOX || + aWidgetType == NS_THEME_STATUSBAR || + aWidgetType == NS_THEME_RESIZER || aWidgetType == NS_THEME_TABPANEL || + aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL || + aWidgetType == NS_THEME_SCROLLBAR_VERTICAL || + aWidgetType == NS_THEME_MENUITEM || aWidgetType == NS_THEME_CHECKMENUITEM || + aWidgetType == NS_THEME_RADIOMENUITEM || aWidgetType == NS_THEME_MENUPOPUP || + aWidgetType == NS_THEME_MENUIMAGE || aWidgetType == NS_THEME_MENUITEMTEXT || + aWidgetType == NS_THEME_SEPARATOR || + aWidgetType == NS_THEME_WINDOW_TITLEBAR || + aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED || + aWidgetType == NS_THEME_WIN_GLASS || aWidgetType == NS_THEME_WIN_BORDERLESS_GLASS) + return NS_OK; // Don't worry about it. + + int32_t part, state; + rv = GetThemePartAndState(aFrame, aWidgetType, part, state); + if (NS_FAILED(rv)) + return rv; + + if (aWidgetType == NS_THEME_TOOLBAR) { + // make space for the separator line above all toolbars but the first + if (state == 0) + aResult->top = TB_SEPARATOR_HEIGHT; + return NS_OK; + } + + // Get our info. + RECT outerRect; // Create a fake outer rect. + outerRect.top = outerRect.left = 100; + outerRect.right = outerRect.bottom = 200; + RECT contentRect(outerRect); + HRESULT res = GetThemeBackgroundContentRect(theme, nullptr, part, state, &outerRect, &contentRect); + + if (FAILED(res)) + return NS_ERROR_FAILURE; + + // Now compute the delta in each direction and place it in our + // nsIntMargin struct. + aResult->top = contentRect.top - outerRect.top; + aResult->bottom = outerRect.bottom - contentRect.bottom; + aResult->left = contentRect.left - outerRect.left; + aResult->right = outerRect.right - contentRect.right; + + // Remove the edges for tabs that are before or after the selected tab, + if (aWidgetType == NS_THEME_TAB) { + if (IsLeftToSelectedTab(aFrame)) + // Remove the right edge, since we won't be drawing it. + aResult->right = 0; + else if (IsRightToSelectedTab(aFrame)) + // Remove the left edge, since we won't be drawing it. + aResult->left = 0; + } + + if (aFrame && (aWidgetType == NS_THEME_NUMBER_INPUT || + aWidgetType == NS_THEME_TEXTFIELD || + aWidgetType == NS_THEME_TEXTFIELD_MULTILINE)) { + nsIContent* content = aFrame->GetContent(); + if (content && content->IsHTMLElement()) { + // We need to pad textfields by 1 pixel, since the caret will draw + // flush against the edge by default if we don't. + aResult->top++; + aResult->left++; + aResult->bottom++; + aResult->right++; + } + } + + ScaleForFrameDPI(aResult, aFrame); + return rv; +} + +bool +nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) +{ + switch (aWidgetType) { + // Radios and checkboxes return a fixed size in GetMinimumWidgetSize + // and have a meaningful baseline, so they can't have + // author-specified padding. + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + aResult->SizeTo(0, 0, 0, 0); + return true; + } + + bool ok = true; + + if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX || + aWidgetType == NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED) { + aResult->SizeTo(0, 0, 0, 0); + + // aero glass doesn't display custom buttons + if (nsUXThemeData::CheckForCompositor()) + return true; + + // button padding for standard windows + if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX) { + aResult->top = GetSystemMetrics(SM_CXFRAME); + } + ScaleForFrameDPI(aResult, aFrame); + return ok; + } + + // Content padding + if (aWidgetType == NS_THEME_WINDOW_TITLEBAR || + aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) { + aResult->SizeTo(0, 0, 0, 0); + // XXX Maximized windows have an offscreen offset equal to + // the border padding. This should be addressed in nsWindow, + // but currently can't be, see UpdateNonClientMargins. + if (aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) + aResult->top = GetSystemMetrics(SM_CXFRAME) + + GetSystemMetrics(SM_CXPADDEDBORDER); + return ok; + } + + HANDLE theme = GetTheme(aWidgetType); + if (!theme) { + ok = ClassicGetWidgetPadding(aContext, aFrame, aWidgetType, aResult); + ScaleForFrameDPI(aResult, aFrame); + return ok; + } + + if (aWidgetType == NS_THEME_MENUPOPUP) + { + SIZE popupSize; + GetThemePartSize(theme, nullptr, MENU_POPUPBORDERS, /* state */ 0, nullptr, TS_TRUE, &popupSize); + aResult->top = aResult->bottom = popupSize.cy; + aResult->left = aResult->right = popupSize.cx; + ScaleForFrameDPI(aResult, aFrame); + return ok; + } + + if (IsVistaOrLater()) { + if (aWidgetType == NS_THEME_NUMBER_INPUT || + aWidgetType == NS_THEME_TEXTFIELD || + aWidgetType == NS_THEME_TEXTFIELD_MULTILINE || + aWidgetType == NS_THEME_MENULIST) + { + /* If we have author-specified padding for these elements, don't do the fixups below */ + if (aFrame->PresContext()->HasAuthorSpecifiedRules(aFrame, NS_AUTHOR_SPECIFIED_PADDING)) + return false; + } + + /* textfields need extra pixels on all sides, otherwise they + * wrap their content too tightly. The actual border is drawn 1px + * inside the specified rectangle, so Gecko will end up making the + * contents look too small. Instead, we add 2px padding for the + * contents and fix this. (Used to be 1px added, see bug 430212) + */ + if (aWidgetType == NS_THEME_NUMBER_INPUT || + aWidgetType == NS_THEME_TEXTFIELD || + aWidgetType == NS_THEME_TEXTFIELD_MULTILINE) { + aResult->top = aResult->bottom = 2; + aResult->left = aResult->right = 2; + ScaleForFrameDPI(aResult, aFrame); + return ok; + } else if (IsHTMLContent(aFrame) && aWidgetType == NS_THEME_MENULIST) { + /* For content menulist controls, we need an extra pixel so + * that we have room to draw our focus rectangle stuff. + * Otherwise, the focus rect might overlap the control's + * border. + */ + aResult->top = aResult->bottom = 1; + aResult->left = aResult->right = 1; + ScaleForFrameDPI(aResult, aFrame); + return ok; + } + } + + int32_t right, left, top, bottom; + right = left = top = bottom = 0; + switch (aWidgetType) + { + case NS_THEME_MENUIMAGE: + right = 8; + left = 3; + break; + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + right = 8; + left = 0; + break; + case NS_THEME_MENUITEMTEXT: + // There seem to be exactly 4 pixels from the edge + // of the gutter to the text: 2px margin (CSS) + 2px padding (here) + { + SIZE size(GetGutterSize(theme, nullptr)); + left = size.cx + 2; + } + break; + case NS_THEME_MENUSEPARATOR: + { + SIZE size(GetGutterSize(theme, nullptr)); + left = size.cx + 5; + top = 10; + bottom = 7; + } + break; + default: + return false; + } + + if (IsFrameRTL(aFrame)) + { + aResult->right = left; + aResult->left = right; + } + else + { + aResult->right = right; + aResult->left = left; + } + + ScaleForFrameDPI(aResult, aFrame); + return ok; +} + +bool +nsNativeThemeWin::GetWidgetOverflow(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsRect* aOverflowRect) +{ + /* This is disabled for now, because it causes invalidation problems -- + * see bug 420381. The effect of not updating the overflow area is that + * for dropdown buttons in content areas, there is a 1px border on 3 sides + * where, if invalidated, the dropdown control probably won't be repainted. + * This is fairly minor, as by default there is nothing in that area, and + * a border only shows up if the widget is being hovered. + */ +#if 0 + if (IsVistaOrLater()) { + /* We explicitly draw dropdown buttons in HTML content 1px bigger + * up, right, and bottom so that they overlap the dropdown's border + * like they're supposed to. + */ + if (aWidgetType == NS_THEME_MENULIST_BUTTON && + IsHTMLContent(aFrame) && + !IsWidgetStyled(aFrame->GetParent()->PresContext(), + aFrame->GetParent(), + NS_THEME_MENULIST)) + { + int32_t p2a = aContext->AppUnitsPerDevPixel(); + /* Note: no overflow on the left */ + nsMargin m(p2a, p2a, p2a, 0); + aOverflowRect->Inflate (m); + return true; + } + } +#endif + + if (aWidgetType == NS_THEME_FOCUS_OUTLINE) { + nsIntMargin border; + nsresult rv = GetWidgetBorder(aContext, aFrame, aWidgetType, &border); + if (NS_SUCCEEDED(rv)) { + int32_t p2a = aContext->AppUnitsPerDevPixel(); + nsMargin m(NSIntPixelsToAppUnits(border.top, p2a), + NSIntPixelsToAppUnits(border.right, p2a), + NSIntPixelsToAppUnits(border.bottom, p2a), + NSIntPixelsToAppUnits(border.left, p2a)); + aOverflowRect->Inflate(m); + return true; + } + } + + return false; +} + +NS_IMETHODIMP +nsNativeThemeWin::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame, + uint8_t aWidgetType, + LayoutDeviceIntSize* aResult, bool* aIsOverridable) +{ + aResult->width = aResult->height = 0; + *aIsOverridable = true; + nsresult rv = NS_OK; + + HANDLE theme = GetTheme(aWidgetType); + if (!theme) { + rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult, aIsOverridable); + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + switch (aWidgetType) { + case NS_THEME_GROUPBOX: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TOOLBOX: + case NS_THEME_WIN_MEDIA_TOOLBOX: + case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX: + case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX: + case NS_THEME_TOOLBAR: + case NS_THEME_STATUSBAR: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_TABPANELS: + case NS_THEME_TABPANEL: + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_MENUITEMTEXT: + case NS_THEME_WIN_GLASS: + case NS_THEME_WIN_BORDERLESS_GLASS: + return NS_OK; // Don't worry about it. + } + + if (aWidgetType == NS_THEME_MENUITEM && IsTopLevelMenu(aFrame)) + return NS_OK; // Don't worry about it for top level menus + + // Call GetSystemMetrics to determine size for WinXP scrollbars + // (GetThemeSysSize API returns the optimal size for the theme, but + // Windows appears to always use metrics when drawing standard scrollbars) + THEMESIZE sizeReq = TS_TRUE; // Best-fit size + switch (aWidgetType) { + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_MENULIST_BUTTON: { + rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult, aIsOverridable); + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + if(!IsTopLevelMenu(aFrame)) + { + SIZE gutterSize(GetGutterSize(theme, nullptr)); + aResult->width = gutterSize.cx; + aResult->height = gutterSize.cy; + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + break; + + case NS_THEME_MENUIMAGE: + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + { + SIZE boxSize(GetGutterSize(theme, nullptr)); + aResult->width = boxSize.cx+2; + aResult->height = boxSize.cy; + *aIsOverridable = false; + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + + case NS_THEME_MENUITEMTEXT: + return NS_OK; + + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + // Best-fit size for progress meters is too large for most + // themes. We want these widgets to be able to really shrink + // down, so use the min-size request value (of 0). + sizeReq = TS_MIN; + break; + + case NS_THEME_RESIZER: + *aIsOverridable = false; + break; + + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: + { + *aIsOverridable = false; + // on Vista, GetThemePartAndState returns odd values for + // scale thumbs, so use a hardcoded size instead. + if (IsVistaOrLater()) { + if (aWidgetType == NS_THEME_SCALETHUMB_HORIZONTAL || + (aWidgetType == NS_THEME_RANGE_THUMB && IsRangeHorizontal(aFrame))) { + aResult->width = 12; + aResult->height = 20; + } + else { + aResult->width = 20; + aResult->height = 12; + } + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + break; + } + + case NS_THEME_SCROLLBAR: + { + if (nsLookAndFeel::GetInt( + nsLookAndFeel::eIntID_UseOverlayScrollbars) != 0) { + aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL), + ::GetSystemMetrics(SM_CYVSCROLL)); + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + break; + } + + case NS_THEME_SEPARATOR: + // that's 2px left margin, 2px right margin and 2px separator + // (the margin is drawn as part of the separator, though) + aResult->width = 6; + ScaleForFrameDPI(aResult, aFrame); + return rv; + + case NS_THEME_BUTTON: + // We should let HTML buttons shrink to their min size. + // FIXME bug 403934: We should probably really separate + // GetPreferredWidgetSize from GetMinimumWidgetSize, so callers can + // use the one they want. + if (aFrame->GetContent()->IsHTMLElement()) { + sizeReq = TS_MIN; + } + break; + + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + // The only way to get accurate titlebar button info is to query a + // window w/buttons when it's visible. nsWindow takes care of this and + // stores that info in nsUXThemeData. + aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_RESTORE].cx; + aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_RESTORE].cy; + // For XP, subtract 4 from system metrics dimensions. + if (!IsVistaOrLater()) { + aResult->width -= 4; + aResult->height -= 4; + } + AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE); + *aIsOverridable = false; + return rv; + + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_MINIMIZE].cx; + aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_MINIMIZE].cy; + if (!IsVistaOrLater()) { + aResult->width -= 4; + aResult->height -= 4; + } + AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE); + *aIsOverridable = false; + return rv; + + case NS_THEME_WINDOW_BUTTON_CLOSE: + aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_CLOSE].cx; + aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_CLOSE].cy; + if (!IsVistaOrLater()) { + aResult->width -= 4; + aResult->height -= 4; + } + AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE); + *aIsOverridable = false; + return rv; + + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + aResult->height = GetSystemMetrics(SM_CYCAPTION); + aResult->height += GetSystemMetrics(SM_CYFRAME); + aResult->height += GetSystemMetrics(SM_CXPADDEDBORDER); + // On Win8.1, we don't want this scaling, because Windows doesn't scale + // the non-client area of the window, and we can end up with ugly overlap + // of the window frame controls into the tab bar or content area. But on + // Win10, we render the window controls ourselves, and the result looks + // better if we do apply this scaling (particularly with themes such as + // DevEdition; see bug 1267636). + if (IsWin10OrLater()) { + ScaleForFrameDPI(aResult, aFrame); + } + *aIsOverridable = false; + return rv; + + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + if (nsUXThemeData::CheckForCompositor()) { + aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cx; + aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cy + - GetSystemMetrics(SM_CYFRAME) + - GetSystemMetrics(SM_CXPADDEDBORDER); + if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED) { + aResult->width += 1; + aResult->height -= 2; + } + *aIsOverridable = false; + return rv; + } + break; + + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + case NS_THEME_WINDOW_FRAME_BOTTOM: + aResult->width = GetSystemMetrics(SM_CXFRAME); + aResult->height = GetSystemMetrics(SM_CYFRAME); + *aIsOverridable = false; + return rv; + } + + int32_t part, state; + rv = GetThemePartAndState(aFrame, aWidgetType, part, state); + if (NS_FAILED(rv)) + return rv; + + HDC hdc = ::GetDC(NULL); + if (!hdc) + return NS_ERROR_FAILURE; + + SIZE sz; + GetThemePartSize(theme, hdc, part, state, nullptr, sizeReq, &sz); + aResult->width = sz.cx; + aResult->height = sz.cy; + + switch(aWidgetType) { + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + aResult->width++; + aResult->height = aResult->height / 2 + 1; + break; + + case NS_THEME_MENUSEPARATOR: + { + SIZE gutterSize(GetGutterSize(theme, hdc)); + aResult->width += gutterSize.cx; + break; + } + + case NS_THEME_MENUARROW: + { + // Use the width of the arrow glyph as padding. See the drawing + // code for details. + aResult->width *= 2; + break; + } + } + + ::ReleaseDC(nullptr, hdc); + + ScaleForFrameDPI(aResult, aFrame); + return rv; +} + +NS_IMETHODIMP +nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, + nsIAtom* aAttribute, bool* aShouldRepaint, + const nsAttrValue* aOldValue) +{ + // Some widget types just never change state. + if (aWidgetType == NS_THEME_TOOLBOX || + aWidgetType == NS_THEME_WIN_MEDIA_TOOLBOX || + aWidgetType == NS_THEME_WIN_COMMUNICATIONS_TOOLBOX || + aWidgetType == NS_THEME_WIN_BROWSERTABBAR_TOOLBOX || + aWidgetType == NS_THEME_TOOLBAR || + aWidgetType == NS_THEME_STATUSBAR || aWidgetType == NS_THEME_STATUSBARPANEL || + aWidgetType == NS_THEME_RESIZERPANEL || + aWidgetType == NS_THEME_PROGRESSCHUNK || + aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL || + aWidgetType == NS_THEME_PROGRESSBAR || + aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL || + aWidgetType == NS_THEME_TOOLTIP || + aWidgetType == NS_THEME_TABPANELS || + aWidgetType == NS_THEME_TABPANEL || + aWidgetType == NS_THEME_SEPARATOR || + aWidgetType == NS_THEME_WIN_GLASS || + aWidgetType == NS_THEME_WIN_BORDERLESS_GLASS) { + *aShouldRepaint = false; + return NS_OK; + } + + if (aWidgetType == NS_THEME_WINDOW_TITLEBAR || + aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED || + aWidgetType == NS_THEME_WINDOW_FRAME_LEFT || + aWidgetType == NS_THEME_WINDOW_FRAME_RIGHT || + aWidgetType == NS_THEME_WINDOW_FRAME_BOTTOM || + aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE || + aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE || + aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE || + aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) { + *aShouldRepaint = true; + return NS_OK; + } + + // On Vista, the scrollbar buttons need to change state when the track has/doesn't have hover + if (!IsVistaOrLater() && + (aWidgetType == NS_THEME_SCROLLBAR_VERTICAL || + aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL)) { + *aShouldRepaint = false; + return NS_OK; + } + + // We need to repaint the dropdown arrow in vista HTML combobox controls when + // the control is closed to get rid of the hover effect. + if (IsVistaOrLater() && + (aWidgetType == NS_THEME_MENULIST || aWidgetType == NS_THEME_MENULIST_BUTTON) && + IsHTMLContent(aFrame)) + { + *aShouldRepaint = true; + return NS_OK; + } + + // XXXdwh Not sure what can really be done here. Can at least guess for + // specific widgets that they're highly unlikely to have certain states. + // For example, a toolbar doesn't care about any states. + if (!aAttribute) { + // Hover/focus/active changed. Always repaint. + *aShouldRepaint = true; + } + else { + // Check the attribute to see if it's relevant. + // disabled, checked, dlgtype, default, etc. + *aShouldRepaint = false; + if (aAttribute == nsGkAtoms::disabled || + aAttribute == nsGkAtoms::checked || + aAttribute == nsGkAtoms::selected || + aAttribute == nsGkAtoms::visuallyselected || + aAttribute == nsGkAtoms::readonly || + aAttribute == nsGkAtoms::open || + aAttribute == nsGkAtoms::menuactive || + aAttribute == nsGkAtoms::focused) + *aShouldRepaint = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsNativeThemeWin::ThemeChanged() +{ + nsUXThemeData::Invalidate(); + return NS_OK; +} + +bool +nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext, + nsIFrame* aFrame, + uint8_t aWidgetType) +{ + // XXXdwh We can go even further and call the API to ask if support exists for + // specific widgets. + + if (aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled()) + return false; + + if (aWidgetType == NS_THEME_FOCUS_OUTLINE) { + return true; + } + + HANDLE theme = nullptr; + if (aWidgetType == NS_THEME_CHECKBOX_CONTAINER) + theme = GetTheme(NS_THEME_CHECKBOX); + else if (aWidgetType == NS_THEME_RADIO_CONTAINER) + theme = GetTheme(NS_THEME_RADIO); + else + theme = GetTheme(aWidgetType); + + if (theme && aWidgetType == NS_THEME_RESIZER) + return true; + + if ((theme) || (!theme && ClassicThemeSupportsWidget(aFrame, aWidgetType))) + // turn off theming for some HTML widgets styled by the page + return (!IsWidgetStyled(aPresContext, aFrame, aWidgetType)); + + return false; +} + +bool +nsNativeThemeWin::WidgetIsContainer(uint8_t aWidgetType) +{ + // XXXdwh At some point flesh all of this out. + if (aWidgetType == NS_THEME_MENULIST_BUTTON || + aWidgetType == NS_THEME_RADIO || + aWidgetType == NS_THEME_CHECKBOX) + return false; + return true; +} + +bool +nsNativeThemeWin::ThemeDrawsFocusForWidget(uint8_t aWidgetType) +{ + return false; +} + +bool +nsNativeThemeWin::ThemeNeedsComboboxDropmarker() +{ + return true; +} + +bool +nsNativeThemeWin::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) +{ + switch (aWidgetType) { + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + case NS_THEME_WINDOW_FRAME_BOTTOM: + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + return true; + default: + return false; + } +} + +nsITheme::ThemeGeometryType +nsNativeThemeWin::ThemeGeometryTypeForWidget(nsIFrame* aFrame, + uint8_t aWidgetType) +{ + switch (aWidgetType) { + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + return eThemeGeometryTypeWindowButtons; + default: + return eThemeGeometryTypeUnknown; + } +} + +bool +nsNativeThemeWin::ShouldHideScrollbars() +{ + return WinUtils::ShouldHideScrollbars(); +} + +nsITheme::Transparency +nsNativeThemeWin::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType) +{ + switch (aWidgetType) { + case NS_THEME_SCROLLBAR_SMALL: + case NS_THEME_SCROLLBAR: + case NS_THEME_STATUSBAR: + // Knowing that scrollbars and statusbars are opaque improves + // performance, because we create layers for them. This better be + // true across all Windows themes! If it's not true, we should + // paint an opaque background for them to make it true! + return eOpaque; + case NS_THEME_WIN_GLASS: + case NS_THEME_WIN_BORDERLESS_GLASS: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_RANGE: + return eTransparent; + } + + HANDLE theme = GetTheme(aWidgetType); + // For the classic theme we don't really have a way of knowing + if (!theme) { + // menu backgrounds and tooltips which can't be themed are opaque + if (aWidgetType == NS_THEME_MENUPOPUP || aWidgetType == NS_THEME_TOOLTIP) { + return eOpaque; + } + return eUnknownTransparency; + } + + int32_t part, state; + nsresult rv = GetThemePartAndState(aFrame, aWidgetType, part, state); + // Fail conservatively + NS_ENSURE_SUCCESS(rv, eUnknownTransparency); + + if (part <= 0) { + // Not a real part code, so IsThemeBackgroundPartiallyTransparent may + // not work, so don't call it. + return eUnknownTransparency; + } + + if (IsThemeBackgroundPartiallyTransparent(theme, part, state)) + return eTransparent; + return eOpaque; +} + +/* Windows 9x/NT/2000/Classic XP Theme Support */ + +bool +nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame, + uint8_t aWidgetType) +{ + switch (aWidgetType) { + case NS_THEME_RESIZER: + { + // The classic native resizer has an opaque grey background which doesn't + // match the usually white background of the scrollable container, so + // only support the native resizer if not in a scrollframe. + nsIFrame* parentFrame = aFrame->GetParent(); + return (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame); + } + case NS_THEME_MENUBAR: + case NS_THEME_MENUPOPUP: + // Classic non-flat menus are handled almost entirely through CSS. + if (!nsUXThemeData::sFlatMenus) + return false; + case NS_THEME_BUTTON: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + case NS_THEME_RANGE: + case NS_THEME_RANGE_THUMB: + case NS_THEME_GROUPBOX: + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBAR_NON_DISAPPEARING: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: + case NS_THEME_MENULIST_BUTTON: + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_MENULIST_TEXTFIELD: + case NS_THEME_MENULIST: + case NS_THEME_TOOLTIP: + case NS_THEME_STATUSBAR: + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_TAB: + case NS_THEME_TABPANEL: + case NS_THEME_TABPANELS: + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + case NS_THEME_MENUARROW: + case NS_THEME_MENUSEPARATOR: + case NS_THEME_MENUITEMTEXT: + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + case NS_THEME_WINDOW_FRAME_BOTTOM: + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + return true; + } + return false; +} + +nsresult +nsNativeThemeWin::ClassicGetWidgetBorder(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) +{ + switch (aWidgetType) { + case NS_THEME_GROUPBOX: + case NS_THEME_BUTTON: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 2; + break; + case NS_THEME_STATUSBAR: + (*aResult).bottom = (*aResult).left = (*aResult).right = 0; + (*aResult).top = 2; + break; + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_MENULIST: + case NS_THEME_MENULIST_TEXTFIELD: + case NS_THEME_TAB: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_FOCUS_OUTLINE: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 2; + break; + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: { + (*aResult).top = 1; + (*aResult).left = 1; + (*aResult).bottom = 1; + (*aResult).right = aFrame->GetNextSibling() ? 3 : 1; + break; + } + case NS_THEME_TOOLTIP: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1; + break; + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1; + break; + case NS_THEME_MENUBAR: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 0; + break; + case NS_THEME_MENUPOPUP: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 3; + break; + default: + (*aResult).top = (*aResult).bottom = (*aResult).left = (*aResult).right = 0; + break; + } + return NS_OK; +} + +bool +nsNativeThemeWin::ClassicGetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) +{ + switch (aWidgetType) { + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: { + int32_t part, state; + bool focused; + + if (NS_FAILED(ClassicGetThemePartAndState(aFrame, aWidgetType, part, state, focused))) + return false; + + if (part == 1) { // top-level menu + if (nsUXThemeData::sFlatMenus || !(state & DFCS_PUSHED)) { + (*aResult).top = (*aResult).bottom = (*aResult).left = (*aResult).right = 2; + } + else { + // make top-level menus look sunken when pushed in the Classic look + (*aResult).top = (*aResult).left = 3; + (*aResult).bottom = (*aResult).right = 1; + } + } + else { + (*aResult).top = 0; + (*aResult).bottom = (*aResult).left = (*aResult).right = 2; + } + return true; + } + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1; + return true; + default: + return false; + } +} + +nsresult +nsNativeThemeWin::ClassicGetMinimumWidgetSize(nsIFrame* aFrame, + uint8_t aWidgetType, + LayoutDeviceIntSize* aResult, + bool* aIsOverridable) +{ + (*aResult).width = (*aResult).height = 0; + *aIsOverridable = true; + switch (aWidgetType) { + case NS_THEME_RADIO: + case NS_THEME_CHECKBOX: + (*aResult).width = (*aResult).height = 13; + break; + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + case NS_THEME_MENUARROW: + (*aResult).width = ::GetSystemMetrics(SM_CXMENUCHECK); + (*aResult).height = ::GetSystemMetrics(SM_CYMENUCHECK); + break; + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL); + (*aResult).height = 8; // No good metrics available for this + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL); + (*aResult).height = ::GetSystemMetrics(SM_CYVSCROLL); + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + (*aResult).width = ::GetSystemMetrics(SM_CXHSCROLL); + (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL); + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBAR_VERTICAL: + // XXX HACK We should be able to have a minimum height for the scrollbar + // track. However, this causes problems when uncollapsing a scrollbar + // inside a tree. See bug 201379 for details. + + // (*aResult).height = ::GetSystemMetrics(SM_CYVTHUMB) << 1; + break; + case NS_THEME_SCROLLBAR_NON_DISAPPEARING: + { + aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL), + ::GetSystemMetrics(SM_CYVSCROLL)); + break; + } + case NS_THEME_RANGE_THUMB: { + if (IsRangeHorizontal(aFrame)) { + (*aResult).width = 12; + (*aResult).height = 20; + } else { + (*aResult).width = 20; + (*aResult).height = 12; + } + *aIsOverridable = false; + break; + } + case NS_THEME_SCALETHUMB_HORIZONTAL: + (*aResult).width = 12; + (*aResult).height = 20; + *aIsOverridable = false; + break; + case NS_THEME_SCALETHUMB_VERTICAL: + (*aResult).width = 20; + (*aResult).height = 12; + *aIsOverridable = false; + break; + case NS_THEME_MENULIST_BUTTON: + (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL); + break; + case NS_THEME_MENULIST: + case NS_THEME_BUTTON: + case NS_THEME_GROUPBOX: + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_MENULIST_TEXTFIELD: + case NS_THEME_STATUSBAR: + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_TOOLTIP: + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_TAB: + case NS_THEME_TABPANEL: + case NS_THEME_TABPANELS: + // no minimum widget size + break; + case NS_THEME_RESIZER: { + NONCLIENTMETRICS nc; + nc.cbSize = sizeof(nc); + if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(nc), &nc, 0)) + (*aResult).width = (*aResult).height = abs(nc.lfStatusFont.lfHeight) + 4; + else + (*aResult).width = (*aResult).height = 15; + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL); + (*aResult).height = ::GetSystemMetrics(SM_CYVTHUMB); + // Without theming, divide the thumb size by two in order to look more + // native + if (!GetTheme(aWidgetType)) + (*aResult).height >>= 1; + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB); + (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL); + // Without theming, divide the thumb size by two in order to look more + // native + if (!GetTheme(aWidgetType)) + (*aResult).width >>= 1; + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBAR_HORIZONTAL: + (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB) << 1; + break; + } + case NS_THEME_MENUSEPARATOR: + { + aResult->width = 0; + aResult->height = 10; + break; + } + + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case NS_THEME_WINDOW_TITLEBAR: + aResult->height = GetSystemMetrics(SM_CYCAPTION); + aResult->height += GetSystemMetrics(SM_CYFRAME); + aResult->width = 0; + break; + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + aResult->width = GetSystemMetrics(SM_CXFRAME); + aResult->height = 0; + break; + + case NS_THEME_WINDOW_FRAME_BOTTOM: + aResult->height = GetSystemMetrics(SM_CYFRAME); + aResult->width = 0; + break; + + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + aResult->width = GetSystemMetrics(SM_CXSIZE); + aResult->height = GetSystemMetrics(SM_CYSIZE); + // XXX I have no idea why these caption metrics are always off, + // but they are. + aResult->width -= 2; + aResult->height -= 4; + if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) { + AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE || + aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) { + AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) { + AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE); + } + break; + + default: + return NS_ERROR_FAILURE; + } + return NS_OK; +} + + +nsresult nsNativeThemeWin::ClassicGetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType, + int32_t& aPart, int32_t& aState, bool& aFocused) +{ + aFocused = false; + switch (aWidgetType) { + case NS_THEME_BUTTON: { + EventStates contentState; + + aPart = DFC_BUTTON; + aState = DFCS_BUTTONPUSH; + aFocused = false; + + contentState = GetContentState(aFrame, aWidgetType); + if (IsDisabled(aFrame, contentState)) + aState |= DFCS_INACTIVE; + else if (IsOpenButton(aFrame)) + aState |= DFCS_PUSHED; + else if (IsCheckedButton(aFrame)) + aState |= DFCS_CHECKED; + else { + if (contentState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) { + aState |= DFCS_PUSHED; + const nsStyleUserInterface *uiData = aFrame->StyleUserInterface(); + // The down state is flat if the button is focusable + if (uiData->mUserFocus == StyleUserFocus::Normal) { + if (!aFrame->GetContent()->IsHTMLElement()) + aState |= DFCS_FLAT; + + aFocused = true; + } + } + if (contentState.HasState(NS_EVENT_STATE_FOCUS) || + (aState == DFCS_BUTTONPUSH && IsDefaultButton(aFrame))) { + aFocused = true; + } + + } + + return NS_OK; + } + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: { + EventStates contentState; + aFocused = false; + + aPart = DFC_BUTTON; + aState = 0; + nsIContent* content = aFrame->GetContent(); + bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX); + bool isChecked = GetCheckedOrSelected(aFrame, !isCheckbox); + bool isIndeterminate = isCheckbox && GetIndeterminate(aFrame); + + if (isCheckbox) { + // indeterminate state takes precedence over checkedness. + if (isIndeterminate) { + aState = DFCS_BUTTON3STATE | DFCS_CHECKED; + } else { + aState = DFCS_BUTTONCHECK; + } + } else { + aState = DFCS_BUTTONRADIO; + } + if (isChecked) { + aState |= DFCS_CHECKED; + } + + contentState = GetContentState(aFrame, aWidgetType); + if (!content->IsXULElement() && + contentState.HasState(NS_EVENT_STATE_FOCUS)) { + aFocused = true; + } + + if (IsDisabled(aFrame, contentState)) { + aState |= DFCS_INACTIVE; + } else if (contentState.HasAllStates(NS_EVENT_STATE_ACTIVE | + NS_EVENT_STATE_HOVER)) { + aState |= DFCS_PUSHED; + } + + return NS_OK; + } + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: { + bool isTopLevel = false; + bool isOpen = false; + nsMenuFrame *menuFrame = do_QueryFrame(aFrame); + EventStates eventState = GetContentState(aFrame, aWidgetType); + + // We indicate top-level-ness using aPart. 0 is a normal menu item, + // 1 is a top-level menu item. The state of the item is composed of + // DFCS_* flags only. + aPart = 0; + aState = 0; + + if (menuFrame) { + // If this is a real menu item, we should check if it is part of the + // main menu bar or not, and if it is a container, as these affect + // rendering. + isTopLevel = menuFrame->IsOnMenuBar(); + isOpen = menuFrame->IsOpen(); + } + + if (IsDisabled(aFrame, eventState)) + aState |= DFCS_INACTIVE; + + if (isTopLevel) { + aPart = 1; + if (isOpen) + aState |= DFCS_PUSHED; + } + + if (IsMenuActive(aFrame, aWidgetType)) + aState |= DFCS_HOT; + + return NS_OK; + } + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + case NS_THEME_MENUARROW: { + aState = 0; + EventStates eventState = GetContentState(aFrame, aWidgetType); + + if (IsDisabled(aFrame, eventState)) + aState |= DFCS_INACTIVE; + if (IsMenuActive(aFrame, aWidgetType)) + aState |= DFCS_HOT; + + if (aWidgetType == NS_THEME_MENUCHECKBOX || aWidgetType == NS_THEME_MENURADIO) { + if (IsCheckedButton(aFrame)) + aState |= DFCS_CHECKED; + } else if (IsFrameRTL(aFrame)) { + aState |= DFCS_RTL; + } + return NS_OK; + } + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_FOCUS_OUTLINE: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_MENULIST: + case NS_THEME_MENULIST_TEXTFIELD: + case NS_THEME_RANGE: + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: + case NS_THEME_STATUSBAR: + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_TOOLTIP: + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_TAB: + case NS_THEME_TABPANEL: + case NS_THEME_TABPANELS: + case NS_THEME_MENUBAR: + case NS_THEME_MENUPOPUP: + case NS_THEME_GROUPBOX: + // these don't use DrawFrameControl + return NS_OK; + case NS_THEME_MENULIST_BUTTON: { + + aPart = DFC_SCROLL; + aState = DFCS_SCROLLCOMBOBOX; + + nsIFrame* parentFrame = aFrame->GetParent(); + bool isHTML = IsHTMLContent(aFrame); + bool isMenulist = !isHTML && parentFrame->GetType() == nsGkAtoms::menuFrame; + bool isOpen = false; + + // HTML select and XUL menulist dropdown buttons get state from the parent. + if (isHTML || isMenulist) + aFrame = parentFrame; + + EventStates eventState = GetContentState(aFrame, aWidgetType); + + if (IsDisabled(aFrame, eventState)) { + aState |= DFCS_INACTIVE; + return NS_OK; + } + + if (isHTML) { + nsIComboboxControlFrame* ccf = do_QueryFrame(aFrame); + isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup()); + } + else + isOpen = IsOpenButton(aFrame); + + // XXX Button should look active until the mouse is released, but + // without making it look active when the popup is clicked. + if (isOpen && (isHTML || isMenulist)) + return NS_OK; + + // Dropdown button active state doesn't need :hover. + if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) + aState |= DFCS_PUSHED | DFCS_FLAT; + + return NS_OK; + } + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: { + EventStates contentState = GetContentState(aFrame, aWidgetType); + + aPart = DFC_SCROLL; + switch (aWidgetType) { + case NS_THEME_SCROLLBARBUTTON_UP: + aState = DFCS_SCROLLUP; + break; + case NS_THEME_SCROLLBARBUTTON_DOWN: + aState = DFCS_SCROLLDOWN; + break; + case NS_THEME_SCROLLBARBUTTON_LEFT: + aState = DFCS_SCROLLLEFT; + break; + case NS_THEME_SCROLLBARBUTTON_RIGHT: + aState = DFCS_SCROLLRIGHT; + break; + } + + if (IsDisabled(aFrame, contentState)) + aState |= DFCS_INACTIVE; + else { + if (contentState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + aState |= DFCS_PUSHED | DFCS_FLAT; + } + + return NS_OK; + } + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: { + EventStates contentState = GetContentState(aFrame, aWidgetType); + + aPart = DFC_SCROLL; + switch (aWidgetType) { + case NS_THEME_SPINNER_UPBUTTON: + aState = DFCS_SCROLLUP; + break; + case NS_THEME_SPINNER_DOWNBUTTON: + aState = DFCS_SCROLLDOWN; + break; + } + + if (IsDisabled(aFrame, contentState)) + aState |= DFCS_INACTIVE; + else { + if (contentState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + aState |= DFCS_PUSHED; + } + + return NS_OK; + } + case NS_THEME_RESIZER: + aPart = DFC_SCROLL; + aState = (IsFrameRTL(aFrame)) ? + DFCS_SCROLLSIZEGRIPRIGHT : DFCS_SCROLLSIZEGRIP; + return NS_OK; + case NS_THEME_MENUSEPARATOR: + aPart = 0; + aState = 0; + return NS_OK; + case NS_THEME_WINDOW_TITLEBAR: + aPart = mozilla::widget::themeconst::WP_CAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + aPart = mozilla::widget::themeconst::WP_MAXCAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_LEFT: + aPart = mozilla::widget::themeconst::WP_FRAMELEFT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_RIGHT: + aPart = mozilla::widget::themeconst::WP_FRAMERIGHT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_BOTTOM: + aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_CLOSE: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONCLOSE | + GetClassicWindowFrameButtonState(GetContentState(aFrame, + aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONMIN | + GetClassicWindowFrameButtonState(GetContentState(aFrame, + aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONMAX | + GetClassicWindowFrameButtonState(GetContentState(aFrame, + aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_RESTORE: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONRESTORE | + GetClassicWindowFrameButtonState(GetContentState(aFrame, + aWidgetType)); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +// Draw classic Windows tab +// (no system API for this, but DrawEdge can draw all the parts of a tab) +static void DrawTab(HDC hdc, const RECT& R, int32_t aPosition, bool aSelected, + bool aDrawLeft, bool aDrawRight) +{ + int32_t leftFlag, topFlag, rightFlag, lightFlag, shadeFlag; + RECT topRect, sideRect, bottomRect, lightRect, shadeRect; + int32_t selectedOffset, lOffset, rOffset; + + selectedOffset = aSelected ? 1 : 0; + lOffset = aDrawLeft ? 2 : 0; + rOffset = aDrawRight ? 2 : 0; + + // Get info for tab orientation/position (Left, Top, Right, Bottom) + switch (aPosition) { + case BF_LEFT: + leftFlag = BF_TOP; topFlag = BF_LEFT; + rightFlag = BF_BOTTOM; + lightFlag = BF_DIAGONAL_ENDTOPRIGHT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT; + + ::SetRect(&topRect, R.left, R.top+lOffset, R.right, R.bottom-rOffset); + ::SetRect(&sideRect, R.left+2, R.top, R.right-2+selectedOffset, R.bottom); + ::SetRect(&bottomRect, R.right-2, R.top, R.right, R.bottom); + ::SetRect(&lightRect, R.left, R.top, R.left+3, R.top+3); + ::SetRect(&shadeRect, R.left+1, R.bottom-2, R.left+2, R.bottom-1); + break; + case BF_TOP: + leftFlag = BF_LEFT; topFlag = BF_TOP; + rightFlag = BF_RIGHT; + lightFlag = BF_DIAGONAL_ENDTOPRIGHT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT; + + ::SetRect(&topRect, R.left+lOffset, R.top, R.right-rOffset, R.bottom); + ::SetRect(&sideRect, R.left, R.top+2, R.right, R.bottom-1+selectedOffset); + ::SetRect(&bottomRect, R.left, R.bottom-1, R.right, R.bottom); + ::SetRect(&lightRect, R.left, R.top, R.left+3, R.top+3); + ::SetRect(&shadeRect, R.right-2, R.top+1, R.right-1, R.top+2); + break; + case BF_RIGHT: + leftFlag = BF_TOP; topFlag = BF_RIGHT; + rightFlag = BF_BOTTOM; + lightFlag = BF_DIAGONAL_ENDTOPLEFT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT; + + ::SetRect(&topRect, R.left, R.top+lOffset, R.right, R.bottom-rOffset); + ::SetRect(&sideRect, R.left+2-selectedOffset, R.top, R.right-2, R.bottom); + ::SetRect(&bottomRect, R.left, R.top, R.left+2, R.bottom); + ::SetRect(&lightRect, R.right-3, R.top, R.right-1, R.top+2); + ::SetRect(&shadeRect, R.right-2, R.bottom-3, R.right, R.bottom-1); + break; + case BF_BOTTOM: + leftFlag = BF_LEFT; topFlag = BF_BOTTOM; + rightFlag = BF_RIGHT; + lightFlag = BF_DIAGONAL_ENDTOPLEFT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT; + + ::SetRect(&topRect, R.left+lOffset, R.top, R.right-rOffset, R.bottom); + ::SetRect(&sideRect, R.left, R.top+2-selectedOffset, R.right, R.bottom-2); + ::SetRect(&bottomRect, R.left, R.top, R.right, R.top+2); + ::SetRect(&lightRect, R.left, R.bottom-3, R.left+2, R.bottom-1); + ::SetRect(&shadeRect, R.right-2, R.bottom-3, R.right, R.bottom-1); + break; + default: + MOZ_CRASH(); + } + + // Background + ::FillRect(hdc, &R, (HBRUSH) (COLOR_3DFACE+1) ); + + // Tab "Top" + ::DrawEdge(hdc, &topRect, EDGE_RAISED, BF_SOFT | topFlag); + + // Tab "Bottom" + if (!aSelected) + ::DrawEdge(hdc, &bottomRect, EDGE_RAISED, BF_SOFT | topFlag); + + // Tab "Sides" + if (!aDrawLeft) + leftFlag = 0; + if (!aDrawRight) + rightFlag = 0; + ::DrawEdge(hdc, &sideRect, EDGE_RAISED, BF_SOFT | leftFlag | rightFlag); + + // Tab Diagonal Corners + if (aDrawLeft) + ::DrawEdge(hdc, &lightRect, EDGE_RAISED, BF_SOFT | lightFlag); + + if (aDrawRight) + ::DrawEdge(hdc, &shadeRect, EDGE_RAISED, BF_SOFT | shadeFlag); +} + +static void DrawMenuImage(HDC hdc, const RECT& rc, int32_t aComponent, uint32_t aColor) +{ + // This procedure creates a memory bitmap to contain the check mark, draws + // it into the bitmap (it is a mask image), then composes it onto the menu + // item in appropriate colors. + HDC hMemoryDC = ::CreateCompatibleDC(hdc); + if (hMemoryDC) { + // XXXjgr We should ideally be caching these, but we wont be notified when + // they change currently, so we can't do so easily. Same for the bitmap. + int checkW = ::GetSystemMetrics(SM_CXMENUCHECK); + int checkH = ::GetSystemMetrics(SM_CYMENUCHECK); + + HBITMAP hMonoBitmap = ::CreateBitmap(checkW, checkH, 1, 1, nullptr); + if (hMonoBitmap) { + + HBITMAP hPrevBitmap = (HBITMAP) ::SelectObject(hMemoryDC, hMonoBitmap); + if (hPrevBitmap) { + + // XXXjgr This will go pear-shaped if the image is bigger than the + // provided rect. What should we do? + RECT imgRect = { 0, 0, checkW, checkH }; + POINT imgPos = { + rc.left + (rc.right - rc.left - checkW) / 2, + rc.top + (rc.bottom - rc.top - checkH) / 2 + }; + + // XXXzeniko Windows renders these 1px lower than you'd expect + if (aComponent == DFCS_MENUCHECK || aComponent == DFCS_MENUBULLET) + imgPos.y++; + + ::DrawFrameControl(hMemoryDC, &imgRect, DFC_MENU, aComponent); + COLORREF oldTextCol = ::SetTextColor(hdc, 0x00000000); + COLORREF oldBackCol = ::SetBkColor(hdc, 0x00FFFFFF); + ::BitBlt(hdc, imgPos.x, imgPos.y, checkW, checkH, hMemoryDC, 0, 0, SRCAND); + ::SetTextColor(hdc, ::GetSysColor(aColor)); + ::SetBkColor(hdc, 0x00000000); + ::BitBlt(hdc, imgPos.x, imgPos.y, checkW, checkH, hMemoryDC, 0, 0, SRCPAINT); + ::SetTextColor(hdc, oldTextCol); + ::SetBkColor(hdc, oldBackCol); + ::SelectObject(hMemoryDC, hPrevBitmap); + } + ::DeleteObject(hMonoBitmap); + } + ::DeleteDC(hMemoryDC); + } +} + +void nsNativeThemeWin::DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore, int32_t back, + HBRUSH defaultBack) +{ + static WORD patBits[8] = { + 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 + }; + + HBITMAP patBmp = ::CreateBitmap(8, 8, 1, 1, patBits); + if (patBmp) { + HBRUSH brush = (HBRUSH) ::CreatePatternBrush(patBmp); + if (brush) { + COLORREF oldForeColor = ::SetTextColor(hdc, ::GetSysColor(fore)); + COLORREF oldBackColor = ::SetBkColor(hdc, ::GetSysColor(back)); + POINT vpOrg; + + ::UnrealizeObject(brush); + ::GetViewportOrgEx(hdc, &vpOrg); + ::SetBrushOrgEx(hdc, vpOrg.x + rc.left, vpOrg.y + rc.top, nullptr); + HBRUSH oldBrush = (HBRUSH) ::SelectObject(hdc, brush); + ::FillRect(hdc, &rc, brush); + ::SetTextColor(hdc, oldForeColor); + ::SetBkColor(hdc, oldBackColor); + ::SelectObject(hdc, oldBrush); + ::DeleteObject(brush); + } + else + ::FillRect(hdc, &rc, defaultBack); + + ::DeleteObject(patBmp); + } +} + +nsresult nsNativeThemeWin::ClassicDrawWidgetBackground(nsRenderingContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + const nsRect& aRect, + const nsRect& aDirtyRect) +{ + int32_t part, state; + bool focused; + nsresult rv; + rv = ClassicGetThemePartAndState(aFrame, aWidgetType, part, state, focused); + if (NS_FAILED(rv)) + return rv; + + if (AssumeThemePartAndStateAreTransparent(part, state)) { + return NS_OK; + } + + gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel()); + RECT widgetRect; + gfxRect tr(aRect.x, aRect.y, aRect.width, aRect.height), + dr(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height); + + tr.ScaleInverse(p2a); + dr.ScaleInverse(p2a); + + RefPtr<gfxContext> ctx = aContext->ThebesContext(); + + gfxWindowsNativeDrawing nativeDrawing(ctx, dr, GetWidgetNativeDrawingFlags(aWidgetType)); + +RENDER_AGAIN: + + HDC hdc = nativeDrawing.BeginNativeDrawing(); + if (!hdc) + return NS_ERROR_FAILURE; + + nativeDrawing.TransformToNativeRect(tr, widgetRect); + + rv = NS_OK; + switch (aWidgetType) { + // Draw button + case NS_THEME_BUTTON: { + if (focused) { + // draw dark button focus border first + HBRUSH brush; + brush = ::GetSysColorBrush(COLOR_3DDKSHADOW); + if (brush) + ::FrameRect(hdc, &widgetRect, brush); + InflateRect(&widgetRect, -1, -1); + } + // fall-through... + } + // Draw controls supported by DrawFrameControl + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + case NS_THEME_MENULIST_BUTTON: + case NS_THEME_RESIZER: { + int32_t oldTA; + // setup DC to make DrawFrameControl draw correctly + oldTA = ::SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP); + ::DrawFrameControl(hdc, &widgetRect, part, state); + ::SetTextAlign(hdc, oldTA); + break; + } + // Draw controls with 2px 3D inset border + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_LISTBOX: + case NS_THEME_MENULIST: + case NS_THEME_MENULIST_TEXTFIELD: { + // Draw inset edge + ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + EventStates eventState = GetContentState(aFrame, aWidgetType); + + // Fill in background + if (IsDisabled(aFrame, eventState) || + (aFrame->GetContent()->IsXULElement() && + IsReadOnly(aFrame))) + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1)); + else + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_WINDOW+1)); + + break; + } + case NS_THEME_TREEVIEW: { + // Draw inset edge + ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + + // Fill in window color background + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_WINDOW+1)); + + break; + } + // Draw ToolTip background + case NS_THEME_TOOLTIP: + ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_WINDOWFRAME)); + InflateRect(&widgetRect, -1, -1); + ::FillRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_INFOBK)); + + break; + case NS_THEME_GROUPBOX: + ::DrawEdge(hdc, &widgetRect, EDGE_ETCHED, BF_RECT | BF_ADJUST); + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1)); + break; + // Draw 3D face background controls + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + // Draw 3D border + ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE); + InflateRect(&widgetRect, -1, -1); + // fall through + case NS_THEME_TABPANEL: + case NS_THEME_STATUSBAR: + case NS_THEME_RESIZERPANEL: { + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1)); + + break; + } + // Draw 3D inset statusbar panel + case NS_THEME_STATUSBARPANEL: { + if (aFrame->GetNextSibling()) + widgetRect.right -= 2; // space between sibling status panels + + ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE); + + break; + } + // Draw scrollbar thumb + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_MIDDLE); + + break; + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCALETHUMB_VERTICAL: + case NS_THEME_SCALETHUMB_HORIZONTAL: { + EventStates eventState = GetContentState(aFrame, aWidgetType); + + ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE | BF_ADJUST); + if (IsDisabled(aFrame, eventState)) { + DrawCheckedRect(hdc, widgetRect, COLOR_3DFACE, COLOR_3DHILIGHT, + (HBRUSH) COLOR_3DHILIGHT); + } + + break; + } + // Draw scrollbar track background + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_HORIZONTAL: { + + // Windows fills in the scrollbar track differently + // depending on whether these are equal + DWORD color3D, colorScrollbar, colorWindow; + + color3D = ::GetSysColor(COLOR_3DFACE); + colorWindow = ::GetSysColor(COLOR_WINDOW); + colorScrollbar = ::GetSysColor(COLOR_SCROLLBAR); + + if ((color3D != colorScrollbar) && (colorWindow != colorScrollbar)) + // Use solid brush + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_SCROLLBAR+1)); + else + { + DrawCheckedRect(hdc, widgetRect, COLOR_3DHILIGHT, COLOR_3DFACE, + (HBRUSH) COLOR_SCROLLBAR+1); + } + // XXX should invert the part of the track being clicked here + // but the track is never :active + + break; + } + // Draw scale track background + case NS_THEME_RANGE: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_SCALE_HORIZONTAL: { + const int32_t trackWidth = 4; + // When rounding is necessary, we round the position of the track + // away from the chevron of the thumb to make it look better. + if (aWidgetType == NS_THEME_SCALE_HORIZONTAL || + (aWidgetType == NS_THEME_RANGE && IsRangeHorizontal(aFrame))) { + widgetRect.top += (widgetRect.bottom - widgetRect.top - trackWidth) / 2; + widgetRect.bottom = widgetRect.top + trackWidth; + } + else { + if (!IsFrameRTL(aFrame)) { + widgetRect.left += (widgetRect.right - widgetRect.left - trackWidth) / 2; + widgetRect.right = widgetRect.left + trackWidth; + } else { + widgetRect.right -= (widgetRect.right - widgetRect.left - trackWidth) / 2; + widgetRect.left = widgetRect.right - trackWidth; + } + } + + ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + ::FillRect(hdc, &widgetRect, (HBRUSH) GetStockObject(GRAY_BRUSH)); + + break; + } + case NS_THEME_PROGRESSCHUNK_VERTICAL: + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_HIGHLIGHT+1)); + break; + + case NS_THEME_PROGRESSCHUNK: { + nsIFrame* stateFrame = aFrame->GetParent(); + EventStates eventStates = GetContentState(stateFrame, aWidgetType); + + bool indeterminate = IsIndeterminateProgress(stateFrame, eventStates); + bool vertical = IsVerticalProgress(stateFrame) || + aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL; + + nsIContent* content = aFrame->GetContent(); + if (!indeterminate || !content) { + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_HIGHLIGHT+1)); + break; + } + + RECT overlayRect = + CalculateProgressOverlayRect(aFrame, &widgetRect, vertical, + indeterminate, true); + + ::FillRect(hdc, &overlayRect, (HBRUSH) (COLOR_HIGHLIGHT+1)); + + if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) { + NS_WARNING("unable to animate progress widget!"); + } + break; + } + + // Draw Tab + case NS_THEME_TAB: { + DrawTab(hdc, widgetRect, + IsBottomTab(aFrame) ? BF_BOTTOM : BF_TOP, + IsSelectedTab(aFrame), + !IsRightToSelectedTab(aFrame), + !IsLeftToSelectedTab(aFrame)); + + break; + } + case NS_THEME_TABPANELS: + ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_SOFT | BF_MIDDLE | + BF_LEFT | BF_RIGHT | BF_BOTTOM); + + break; + case NS_THEME_MENUBAR: + break; + case NS_THEME_MENUPOPUP: + NS_ASSERTION(nsUXThemeData::sFlatMenus, "Classic menus are styled entirely through CSS"); + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_MENU+1)); + ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_BTNSHADOW)); + break; + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + // part == 0 for normal items + // part == 1 for top-level menu items + if (nsUXThemeData::sFlatMenus) { + // Not disabled and hot/pushed. + if ((state & (DFCS_HOT | DFCS_PUSHED)) != 0) { + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_MENUHILIGHT+1)); + ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_HIGHLIGHT)); + } + } else { + if (part == 1) { + if ((state & DFCS_INACTIVE) == 0) { + if ((state & DFCS_PUSHED) != 0) { + ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT); + } else if ((state & DFCS_HOT) != 0) { + ::DrawEdge(hdc, &widgetRect, BDR_RAISEDINNER, BF_RECT); + } + } + } else { + if ((state & (DFCS_HOT | DFCS_PUSHED)) != 0) { + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_HIGHLIGHT+1)); + } + } + } + break; + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + if (!(state & DFCS_CHECKED)) + break; // nothin' to do + case NS_THEME_MENUARROW: { + uint32_t color = COLOR_MENUTEXT; + if ((state & DFCS_INACTIVE)) + color = COLOR_GRAYTEXT; + else if ((state & DFCS_HOT)) + color = COLOR_HIGHLIGHTTEXT; + + if (aWidgetType == NS_THEME_MENUCHECKBOX) + DrawMenuImage(hdc, widgetRect, DFCS_MENUCHECK, color); + else if (aWidgetType == NS_THEME_MENURADIO) + DrawMenuImage(hdc, widgetRect, DFCS_MENUBULLET, color); + else if (aWidgetType == NS_THEME_MENUARROW) + DrawMenuImage(hdc, widgetRect, + (state & DFCS_RTL) ? DFCS_MENUARROWRIGHT : DFCS_MENUARROW, + color); + break; + } + case NS_THEME_MENUSEPARATOR: { + // separators are offset by a bit (see menu.css) + widgetRect.left++; + widgetRect.right--; + + // This magic number is brought to you by the value in menu.css + widgetRect.top += 4; + // Our rectangles are 1 pixel high (see border size in menu.css) + widgetRect.bottom = widgetRect.top+1; + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DSHADOW+1)); + widgetRect.top++; + widgetRect.bottom++; + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DHILIGHT+1)); + break; + } + + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + { + RECT rect = widgetRect; + int32_t offset = GetSystemMetrics(SM_CXFRAME); + + // first fill the area to the color of the window background + FillRect(hdc, &rect, (HBRUSH)(COLOR_3DFACE+1)); + + // inset the caption area so it doesn't overflow. + rect.top += offset; + // if enabled, draw a gradient titlebar background, otherwise + // fill with a solid color. + BOOL bFlag = TRUE; + SystemParametersInfo(SPI_GETGRADIENTCAPTIONS, 0, &bFlag, 0); + if (!bFlag) { + if (state == mozilla::widget::themeconst::FS_ACTIVE) + FillRect(hdc, &rect, (HBRUSH)(COLOR_ACTIVECAPTION+1)); + else + FillRect(hdc, &rect, (HBRUSH)(COLOR_INACTIVECAPTION+1)); + } else { + DWORD startColor, endColor; + if (state == mozilla::widget::themeconst::FS_ACTIVE) { + startColor = GetSysColor(COLOR_ACTIVECAPTION); + endColor = GetSysColor(COLOR_GRADIENTACTIVECAPTION); + } else { + startColor = GetSysColor(COLOR_INACTIVECAPTION); + endColor = GetSysColor(COLOR_GRADIENTINACTIVECAPTION); + } + + TRIVERTEX vertex[2]; + vertex[0].x = rect.left; + vertex[0].y = rect.top; + vertex[0].Red = GetRValue(startColor) << 8; + vertex[0].Green = GetGValue(startColor) << 8; + vertex[0].Blue = GetBValue(startColor) << 8; + vertex[0].Alpha = 0; + + vertex[1].x = rect.right; + vertex[1].y = rect.bottom; + vertex[1].Red = GetRValue(endColor) << 8; + vertex[1].Green = GetGValue(endColor) << 8; + vertex[1].Blue = GetBValue(endColor) << 8; + vertex[1].Alpha = 0; + + GRADIENT_RECT gRect; + gRect.UpperLeft = 0; + gRect.LowerRight = 1; + // available on win2k & up + GradientFill(hdc, vertex, 2, &gRect, 1, GRADIENT_FILL_RECT_H); + } + + if (aWidgetType == NS_THEME_WINDOW_TITLEBAR) { + // frame things up with a top raised border. + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_TOP); + } + break; + } + + case NS_THEME_WINDOW_FRAME_LEFT: + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_LEFT); + break; + + case NS_THEME_WINDOW_FRAME_RIGHT: + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RIGHT); + break; + + case NS_THEME_WINDOW_FRAME_BOTTOM: + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_BOTTOM); + break; + + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + { + if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE || + aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE); + } + int32_t oldTA = SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP); + DrawFrameControl(hdc, &widgetRect, part, state); + SetTextAlign(hdc, oldTA); + break; + } + + default: + rv = NS_ERROR_FAILURE; + break; + } + + nativeDrawing.EndNativeDrawing(); + + if (NS_FAILED(rv)) + return rv; + + if (nativeDrawing.ShouldRenderAgain()) + goto RENDER_AGAIN; + + nativeDrawing.PaintToContext(); + + return rv; +} + +uint32_t +nsNativeThemeWin::GetWidgetNativeDrawingFlags(uint8_t aWidgetType) +{ + switch (aWidgetType) { + case NS_THEME_BUTTON: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_FOCUS_OUTLINE: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + + case NS_THEME_MENULIST: + case NS_THEME_MENULIST_TEXTFIELD: + return + gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA | + gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE | + gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM; + + // need to check these others + case NS_THEME_RANGE: + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_TOOLTIP: + case NS_THEME_STATUSBAR: + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_RESIZER: + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_TAB: + case NS_THEME_TABPANEL: + case NS_THEME_TABPANELS: + case NS_THEME_MENUBAR: + case NS_THEME_MENUPOPUP: + case NS_THEME_MENUITEM: + break; + + // the dropdown button /almost/ renders correctly with scaling, + // except that the graphic in the dropdown button (the downward arrow) + // doesn't get scaled up. + case NS_THEME_MENULIST_BUTTON: + // these are definitely no; they're all graphics that don't get scaled up + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + case NS_THEME_GROUPBOX: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + case NS_THEME_MENUARROW: + return + gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA | + gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE | + gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM; + } + + return + gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA | + gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE | + gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM; +} + +/////////////////////////////////////////// +// Creation Routine +/////////////////////////////////////////// + +// from nsWindow.cpp +extern bool gDisableNativeTheme; + +nsresult NS_NewNativeTheme(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + if (gDisableNativeTheme) + return NS_ERROR_NO_INTERFACE; + + if (aOuter) + return NS_ERROR_NO_AGGREGATION; + + nsNativeThemeWin* theme = new nsNativeThemeWin(); + if (!theme) + return NS_ERROR_OUT_OF_MEMORY; + return theme->QueryInterface(aIID, aResult); +} |