summaryrefslogtreecommitdiff
path: root/widget/gtk/nsNativeThemeGTK.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gtk/nsNativeThemeGTK.cpp')
-rw-r--r--widget/gtk/nsNativeThemeGTK.cpp1994
1 files changed, 1994 insertions, 0 deletions
diff --git a/widget/gtk/nsNativeThemeGTK.cpp b/widget/gtk/nsNativeThemeGTK.cpp
new file mode 100644
index 0000000000..89b8ab7dc8
--- /dev/null
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -0,0 +1,1994 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsNativeThemeGTK.h"
+#include "nsThemeConstants.h"
+#include "gtkdrawing.h"
+#include "nsScreenGtk.h"
+
+#include "gfx2DGlue.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+#include "nsIFrame.h"
+#include "nsIPresShell.h"
+#include "nsIContent.h"
+#include "nsViewManager.h"
+#include "nsNameSpaceManager.h"
+#include "nsGfxCIID.h"
+#include "nsTransform2D.h"
+#include "nsMenuFrame.h"
+#include "prlink.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsRenderingContext.h"
+#include "nsGkAtoms.h"
+#include "nsAttrValueInlines.h"
+
+#include "mozilla/EventStates.h"
+#include "mozilla/Services.h"
+
+#include <gdk/gdkprivate.h>
+#include <gtk/gtk.h>
+
+#include "gfxContext.h"
+#include "gfxPlatformGtk.h"
+#include "gfxGdkNativeRenderer.h"
+#include "mozilla/gfx/BorrowedContext.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/gfx/PathHelpers.h"
+
+#ifdef MOZ_X11
+# ifdef CAIRO_HAS_XLIB_SURFACE
+# include "cairo-xlib.h"
+# endif
+# ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
+# include "cairo-xlib-xrender.h"
+# endif
+#endif
+
+#include <algorithm>
+#include <dlfcn.h>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme,
+ nsIObserver)
+
+static int gLastGdkError;
+
+nsNativeThemeGTK::nsNativeThemeGTK()
+{
+ if (moz_gtk_init() != MOZ_GTK_SUCCESS) {
+ memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes));
+ return;
+ }
+
+ // We have to call moz_gtk_shutdown before the event loop stops running.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->AddObserver(this, "xpcom-shutdown", false);
+
+ memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
+ memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
+}
+
+nsNativeThemeGTK::~nsNativeThemeGTK() {
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
+ moz_gtk_shutdown();
+ } else {
+ NS_NOTREACHED("unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void
+nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame)
+{
+ nsIPresShell *shell = GetPresShell(aFrame);
+ if (!shell)
+ return;
+
+ nsViewManager* vm = shell->GetViewManager();
+ if (!vm)
+ return;
+
+ vm->InvalidateAllViews();
+}
+
+
+static bool IsFrameContentNodeInNamespace(nsIFrame *aFrame, uint32_t aNamespace)
+{
+ nsIContent *content = aFrame ? aFrame->GetContent() : nullptr;
+ if (!content)
+ return false;
+ return content->IsInNamespace(aNamespace);
+}
+
+static bool IsWidgetTypeDisabled(uint8_t* aDisabledVector, uint8_t aWidgetType) {
+ return (aDisabledVector[aWidgetType >> 3] & (1 << (aWidgetType & 7))) != 0;
+}
+
+static void SetWidgetTypeDisabled(uint8_t* aDisabledVector, uint8_t aWidgetType) {
+ aDisabledVector[aWidgetType >> 3] |= (1 << (aWidgetType & 7));
+}
+
+static inline uint16_t
+GetWidgetStateKey(uint8_t aWidgetType, GtkWidgetState *aWidgetState)
+{
+ return (aWidgetState->active |
+ aWidgetState->focused << 1 |
+ aWidgetState->inHover << 2 |
+ aWidgetState->disabled << 3 |
+ aWidgetState->isDefault << 4 |
+ aWidgetType << 5);
+}
+
+static bool IsWidgetStateSafe(uint8_t* aSafeVector,
+ uint8_t aWidgetType,
+ GtkWidgetState *aWidgetState)
+{
+ uint8_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
+ return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0;
+}
+
+static void SetWidgetStateSafe(uint8_t *aSafeVector,
+ uint8_t aWidgetType,
+ GtkWidgetState *aWidgetState)
+{
+ uint8_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
+ aSafeVector[key >> 3] |= (1 << (key & 7));
+}
+
+/* static */ GtkTextDirection
+nsNativeThemeGTK::GetTextDirection(nsIFrame* aFrame)
+{
+ // IsFrameRTL() treats vertical-rl modes as right-to-left (in addition to
+ // horizontal text with direction=RTL), rather than just considering the
+ // text direction. GtkTextDirection does not have distinct values for
+ // vertical writing modes, but considering the block flow direction is
+ // important for resizers and scrollbar elements, at least.
+ return IsFrameRTL(aFrame) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
+}
+
+// Returns positive for negative margins (otherwise 0).
+gint
+nsNativeThemeGTK::GetTabMarginPixels(nsIFrame* aFrame)
+{
+ nscoord margin =
+ IsBottomTab(aFrame) ? aFrame->GetUsedMargin().top
+ : aFrame->GetUsedMargin().bottom;
+
+ return std::min<gint>(MOZ_GTK_TAB_MARGIN_MASK,
+ std::max(0,
+ aFrame->PresContext()->AppUnitsToDevPixels(-margin)));
+}
+
+static bool ShouldScrollbarButtonBeDisabled(int32_t aCurpos, int32_t aMaxpos,
+ uint8_t aWidgetType)
+{
+ return ((aCurpos == 0 && (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT))
+ || (aCurpos == aMaxpos && (aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT)));
+}
+
+bool
+nsNativeThemeGTK::GetGtkWidgetAndState(uint8_t aWidgetType, nsIFrame* aFrame,
+ WidgetNodeType& aGtkWidgetType,
+ GtkWidgetState* aState,
+ gint* aWidgetFlags)
+{
+ if (aState) {
+ // For XUL checkboxes and radio buttons, the state of the parent
+ // determines our state.
+ nsIFrame *stateFrame = aFrame;
+ if (aFrame && ((aWidgetFlags && (aWidgetType == NS_THEME_CHECKBOX ||
+ aWidgetType == NS_THEME_RADIO)) ||
+ aWidgetType == NS_THEME_CHECKBOX_LABEL ||
+ aWidgetType == NS_THEME_RADIO_LABEL)) {
+
+ nsIAtom* atom = nullptr;
+ if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
+ if (aWidgetType == NS_THEME_CHECKBOX_LABEL ||
+ aWidgetType == NS_THEME_RADIO_LABEL) {
+ // Adjust stateFrame so GetContentState finds the correct state.
+ stateFrame = aFrame = aFrame->GetParent()->GetParent();
+ } else {
+ // GetContentState knows to look one frame up for radio/checkbox
+ // widgets, so don't adjust stateFrame here.
+ aFrame = aFrame->GetParent();
+ }
+ if (aWidgetFlags) {
+ if (!atom) {
+ atom = (aWidgetType == NS_THEME_CHECKBOX ||
+ aWidgetType == NS_THEME_CHECKBOX_LABEL) ? nsGkAtoms::checked
+ : nsGkAtoms::selected;
+ }
+ *aWidgetFlags = CheckBooleanAttr(aFrame, atom);
+ }
+ } else {
+ if (aWidgetFlags) {
+ nsCOMPtr<nsIDOMHTMLInputElement> inputElt(do_QueryInterface(aFrame->GetContent()));
+ *aWidgetFlags = 0;
+ if (inputElt) {
+ bool isHTMLChecked;
+ inputElt->GetChecked(&isHTMLChecked);
+ if (isHTMLChecked)
+ *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED;
+ }
+
+ if (GetIndeterminate(aFrame))
+ *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT;
+ }
+ }
+ } else if (aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
+ aWidgetType == NS_THEME_TREEHEADERSORTARROW ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_NEXT ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_UP ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_DOWN) {
+ // The state of an arrow comes from its parent.
+ stateFrame = aFrame = aFrame->GetParent();
+ }
+
+ EventStates eventState = GetContentState(stateFrame, aWidgetType);
+
+ aState->disabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
+ aState->active = eventState.HasState(NS_EVENT_STATE_ACTIVE);
+ aState->focused = eventState.HasState(NS_EVENT_STATE_FOCUS);
+ aState->inHover = eventState.HasState(NS_EVENT_STATE_HOVER);
+ aState->isDefault = IsDefaultButton(aFrame);
+ aState->canDefault = FALSE; // XXX fix me
+ aState->depressed = FALSE;
+
+ if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
+ aState->disabled = FALSE;
+ aState->active = FALSE;
+ aState->inHover = FALSE;
+ aState->isDefault = FALSE;
+ aState->canDefault = FALSE;
+
+ aState->focused = TRUE;
+ aState->depressed = TRUE; // see moz_gtk_entry_paint()
+ } else if (aWidgetType == NS_THEME_BUTTON ||
+ aWidgetType == NS_THEME_TOOLBARBUTTON ||
+ aWidgetType == NS_THEME_DUALBUTTON ||
+ aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
+ aWidgetType == NS_THEME_MENULIST ||
+ aWidgetType == NS_THEME_MENULIST_BUTTON) {
+ aState->active &= aState->inHover;
+ }
+
+ if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
+ // For these widget types, some element (either a child or parent)
+ // actually has element focus, so we check the focused attribute
+ // to see whether to draw in the focused state.
+ if (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
+ aWidgetType == NS_THEME_MENULIST_TEXTFIELD ||
+ aWidgetType == NS_THEME_SPINNER_TEXTFIELD ||
+ aWidgetType == NS_THEME_RADIO_CONTAINER ||
+ aWidgetType == NS_THEME_RADIO_LABEL) {
+ aState->focused = IsFocused(aFrame);
+ } else if (aWidgetType == NS_THEME_RADIO ||
+ aWidgetType == NS_THEME_CHECKBOX) {
+ // In XUL, checkboxes and radios shouldn't have focus rings, their labels do
+ aState->focused = FALSE;
+ }
+
+ if (aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL ||
+ aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) {
+ // for scrollbars we need to go up two to go from the thumb to
+ // the slider to the actual scrollbar object
+ nsIFrame *tmpFrame = aFrame->GetParent()->GetParent();
+
+ aState->curpos = CheckIntAttr(tmpFrame, nsGkAtoms::curpos, 0);
+ aState->maxpos = CheckIntAttr(tmpFrame, nsGkAtoms::maxpos, 100);
+
+ if (CheckBooleanAttr(aFrame, nsGkAtoms::active)) {
+ aState->active = TRUE;
+ // Set hover state to emulate Gtk style of active scrollbar thumb
+ aState->inHover = TRUE;
+ }
+ }
+
+ if (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) {
+ // set the state to disabled when the scrollbar is scrolled to
+ // the beginning or the end, depending on the button type.
+ int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
+ int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
+ if (ShouldScrollbarButtonBeDisabled(curpos, maxpos, aWidgetType)) {
+ aState->disabled = true;
+ }
+
+ // In order to simulate native GTK scrollbar click behavior,
+ // we set the active attribute on the element to true if it's
+ // pressed with any mouse button.
+ // This allows us to show that it's active without setting :active
+ else if (CheckBooleanAttr(aFrame, nsGkAtoms::active))
+ aState->active = true;
+
+ if (aWidgetFlags) {
+ *aWidgetFlags = GetScrollbarButtonType(aFrame);
+ if (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP < 2)
+ *aWidgetFlags |= MOZ_GTK_STEPPER_VERTICAL;
+ }
+ }
+
+ // menu item state is determined by the attribute "_moz-menuactive",
+ // and not by the mouse hovering (accessibility). as a special case,
+ // menus which are children of a menu bar are only marked as prelight
+ // if they are open, not on normal hover.
+
+ if (aWidgetType == NS_THEME_MENUITEM ||
+ aWidgetType == NS_THEME_CHECKMENUITEM ||
+ aWidgetType == NS_THEME_RADIOMENUITEM ||
+ aWidgetType == NS_THEME_MENUSEPARATOR ||
+ aWidgetType == NS_THEME_MENUARROW) {
+ bool isTopLevel = false;
+ nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
+ if (menuFrame) {
+ isTopLevel = menuFrame->IsOnMenuBar();
+ }
+
+ if (isTopLevel) {
+ aState->inHover = menuFrame->IsOpen();
+ } else {
+ aState->inHover = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ }
+
+ aState->active = FALSE;
+
+ if (aWidgetType == NS_THEME_CHECKMENUITEM ||
+ aWidgetType == NS_THEME_RADIOMENUITEM) {
+ *aWidgetFlags = 0;
+ if (aFrame && aFrame->GetContent()) {
+ *aWidgetFlags = aFrame->GetContent()->
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eIgnoreCase);
+ }
+ }
+ }
+
+ // A button with drop down menu open or an activated toggle button
+ // should always appear depressed.
+ if (aWidgetType == NS_THEME_BUTTON ||
+ aWidgetType == NS_THEME_TOOLBARBUTTON ||
+ aWidgetType == NS_THEME_DUALBUTTON ||
+ aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
+ aWidgetType == NS_THEME_MENULIST ||
+ aWidgetType == NS_THEME_MENULIST_BUTTON) {
+ bool menuOpen = IsOpenButton(aFrame);
+ aState->depressed = IsCheckedButton(aFrame) || menuOpen;
+ // we must not highlight buttons with open drop down menus on hover.
+ aState->inHover = aState->inHover && !menuOpen;
+ }
+
+ // When the input field of the drop down button has focus, some themes
+ // should draw focus for the drop down button as well.
+ if (aWidgetType == NS_THEME_MENULIST_BUTTON && aWidgetFlags) {
+ *aWidgetFlags = CheckBooleanAttr(aFrame, nsGkAtoms::parentfocused);
+ }
+ }
+ }
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_RELIEF_NORMAL;
+ aGtkWidgetType = MOZ_GTK_BUTTON;
+ break;
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_DUALBUTTON:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_RELIEF_NONE;
+ aGtkWidgetType = MOZ_GTK_TOOLBAR_BUTTON;
+ break;
+ case NS_THEME_FOCUS_OUTLINE:
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+ break;
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ aGtkWidgetType = (aWidgetType == NS_THEME_RADIO) ? MOZ_GTK_RADIOBUTTON : MOZ_GTK_CHECKBUTTON;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_BUTTON;
+ break;
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_VERTICAL;
+ if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque)
+ *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
+ else
+ *aWidgetFlags = 0;
+ break;
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_HORIZONTAL;
+ if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque)
+ *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
+ else
+ *aWidgetFlags = 0;
+ break;
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL;
+ break;
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ break;
+ case NS_THEME_SPINNER:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON;
+ break;
+ case NS_THEME_SPINNER_UPBUTTON:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_UP;
+ break;
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_DOWN;
+ break;
+ case NS_THEME_SPINNER_TEXTFIELD:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_ENTRY;
+ break;
+ case NS_THEME_RANGE:
+ {
+ if (IsRangeHorizontal(aFrame)) {
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
+ } else {
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
+ }
+ break;
+ }
+ case NS_THEME_RANGE_THUMB:
+ {
+ if (IsRangeHorizontal(aFrame)) {
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
+ } else {
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
+ }
+ break;
+ }
+ case NS_THEME_SCALE_HORIZONTAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
+ break;
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
+ break;
+ case NS_THEME_SCALE_VERTICAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
+ break;
+ case NS_THEME_SEPARATOR:
+ aGtkWidgetType = MOZ_GTK_TOOLBAR_SEPARATOR;
+ break;
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
+ break;
+ case NS_THEME_TOOLBARGRIPPER:
+ aGtkWidgetType = MOZ_GTK_GRIPPER;
+ break;
+ case NS_THEME_RESIZER:
+ aGtkWidgetType = MOZ_GTK_RESIZER;
+ break;
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+ break;
+ case NS_THEME_TEXTFIELD_MULTILINE:
+#if (MOZ_WIDGET_GTK == 3)
+ aGtkWidgetType = MOZ_GTK_TEXT_VIEW;
+#else
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+#endif
+ break;
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW;
+ break;
+ case NS_THEME_TREEHEADERCELL:
+ if (aWidgetFlags) {
+ // In this case, the flag denotes whether the header is the sorted one or not
+ if (GetTreeSortDirection(aFrame) == eTreeSortDirection_Natural)
+ *aWidgetFlags = false;
+ else
+ *aWidgetFlags = true;
+ }
+ aGtkWidgetType = MOZ_GTK_TREE_HEADER_CELL;
+ break;
+ case NS_THEME_TREEHEADERSORTARROW:
+ if (aWidgetFlags) {
+ switch (GetTreeSortDirection(aFrame)) {
+ case eTreeSortDirection_Ascending:
+ *aWidgetFlags = GTK_ARROW_DOWN;
+ break;
+ case eTreeSortDirection_Descending:
+ *aWidgetFlags = GTK_ARROW_UP;
+ break;
+ case eTreeSortDirection_Natural:
+ default:
+ /* This prevents the treecolums from getting smaller
+ * and wider when switching sort direction off and on
+ * */
+ *aWidgetFlags = GTK_ARROW_NONE;
+ break;
+ }
+ }
+ aGtkWidgetType = MOZ_GTK_TREE_HEADER_SORTARROW;
+ break;
+ case NS_THEME_TREETWISTY:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_EXPANDER_COLLAPSED;
+ break;
+ case NS_THEME_TREETWISTYOPEN:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_EXPANDER_EXPANDED;
+ break;
+ case NS_THEME_MENULIST:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN;
+ if (aWidgetFlags)
+ *aWidgetFlags = IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML);
+ break;
+ case NS_THEME_MENULIST_TEXT:
+ return false; // nothing to do, but prevents the bg from being drawn
+ case NS_THEME_MENULIST_TEXTFIELD:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN_ENTRY;
+ break;
+ case NS_THEME_MENULIST_BUTTON:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN_ARROW;
+ break;
+ case NS_THEME_TOOLBARBUTTON_DROPDOWN:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_NEXT:
+ case NS_THEME_BUTTON_ARROW_PREVIOUS:
+ aGtkWidgetType = MOZ_GTK_TOOLBARBUTTON_ARROW;
+ if (aWidgetFlags) {
+ *aWidgetFlags = GTK_ARROW_DOWN;
+
+ if (aWidgetType == NS_THEME_BUTTON_ARROW_UP)
+ *aWidgetFlags = GTK_ARROW_UP;
+ else if (aWidgetType == NS_THEME_BUTTON_ARROW_NEXT)
+ *aWidgetFlags = GTK_ARROW_RIGHT;
+ else if (aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS)
+ *aWidgetFlags = GTK_ARROW_LEFT;
+ }
+ break;
+ case NS_THEME_CHECKBOX_CONTAINER:
+ aGtkWidgetType = MOZ_GTK_CHECKBUTTON_CONTAINER;
+ break;
+ case NS_THEME_RADIO_CONTAINER:
+ aGtkWidgetType = MOZ_GTK_RADIOBUTTON_CONTAINER;
+ break;
+ case NS_THEME_CHECKBOX_LABEL:
+ aGtkWidgetType = MOZ_GTK_CHECKBUTTON_LABEL;
+ break;
+ case NS_THEME_RADIO_LABEL:
+ aGtkWidgetType = MOZ_GTK_RADIOBUTTON_LABEL;
+ break;
+ case NS_THEME_TOOLBAR:
+ aGtkWidgetType = MOZ_GTK_TOOLBAR;
+ break;
+ case NS_THEME_TOOLTIP:
+ aGtkWidgetType = MOZ_GTK_TOOLTIP;
+ break;
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ aGtkWidgetType = MOZ_GTK_FRAME;
+ break;
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
+ break;
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ {
+ nsIFrame* stateFrame = aFrame->GetParent();
+ EventStates eventStates = GetContentState(stateFrame, aWidgetType);
+
+ aGtkWidgetType = IsIndeterminateProgress(stateFrame, eventStates)
+ ? IsVerticalProgress(stateFrame)
+ ? MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE
+ : MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE
+ : MOZ_GTK_PROGRESS_CHUNK;
+ }
+ break;
+ case NS_THEME_TAB_SCROLL_ARROW_BACK:
+ case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
+ if (aWidgetFlags)
+ *aWidgetFlags = aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK ?
+ GTK_ARROW_LEFT : GTK_ARROW_RIGHT;
+ aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW;
+ break;
+ case NS_THEME_TABPANELS:
+ aGtkWidgetType = MOZ_GTK_TABPANELS;
+ break;
+ case NS_THEME_TAB:
+ {
+ if (IsBottomTab(aFrame)) {
+ aGtkWidgetType = MOZ_GTK_TAB_BOTTOM;
+ } else {
+ aGtkWidgetType = MOZ_GTK_TAB_TOP;
+ }
+
+ if (aWidgetFlags) {
+ /* First bits will be used to store max(0,-bmargin) where bmargin
+ * is the bottom margin of the tab in pixels (resp. top margin,
+ * for bottom tabs). */
+ *aWidgetFlags = GetTabMarginPixels(aFrame);
+
+ if (IsSelectedTab(aFrame))
+ *aWidgetFlags |= MOZ_GTK_TAB_SELECTED;
+
+ if (IsFirstTab(aFrame))
+ *aWidgetFlags |= MOZ_GTK_TAB_FIRST;
+ }
+ }
+ break;
+ case NS_THEME_SPLITTER:
+ if (IsHorizontal(aFrame))
+ aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL;
+ else
+ aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL;
+ break;
+ case NS_THEME_MENUBAR:
+ aGtkWidgetType = MOZ_GTK_MENUBAR;
+ break;
+ case NS_THEME_MENUPOPUP:
+ aGtkWidgetType = MOZ_GTK_MENUPOPUP;
+ break;
+ case NS_THEME_MENUITEM:
+ {
+ nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
+ if (menuFrame && menuFrame->IsOnMenuBar()) {
+ aGtkWidgetType = MOZ_GTK_MENUBARITEM;
+ break;
+ }
+ }
+ aGtkWidgetType = MOZ_GTK_MENUITEM;
+ break;
+ case NS_THEME_MENUSEPARATOR:
+ aGtkWidgetType = MOZ_GTK_MENUSEPARATOR;
+ break;
+ case NS_THEME_MENUARROW:
+ aGtkWidgetType = MOZ_GTK_MENUARROW;
+ break;
+ case NS_THEME_CHECKMENUITEM:
+ aGtkWidgetType = MOZ_GTK_CHECKMENUITEM;
+ break;
+ case NS_THEME_RADIOMENUITEM:
+ aGtkWidgetType = MOZ_GTK_RADIOMENUITEM;
+ break;
+ case NS_THEME_WINDOW:
+ case NS_THEME_DIALOG:
+ aGtkWidgetType = MOZ_GTK_WINDOW;
+ break;
+ case NS_THEME_GTK_INFO_BAR:
+ aGtkWidgetType = MOZ_GTK_INFO_BAR;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+#if (MOZ_WIDGET_GTK == 2)
+class ThemeRenderer : public gfxGdkNativeRenderer {
+public:
+ ThemeRenderer(GtkWidgetState aState, WidgetNodeType aGTKWidgetType,
+ gint aFlags, GtkTextDirection aDirection,
+ const GdkRectangle& aGDKRect, const GdkRectangle& aGDKClip)
+ : mState(aState), mGTKWidgetType(aGTKWidgetType), mFlags(aFlags),
+ mDirection(aDirection), mGDKRect(aGDKRect), mGDKClip(aGDKClip) {}
+ nsresult DrawWithGDK(GdkDrawable * drawable, gint offsetX, gint offsetY,
+ GdkRectangle * clipRects, uint32_t numClipRects);
+private:
+ GtkWidgetState mState;
+ WidgetNodeType mGTKWidgetType;
+ gint mFlags;
+ GtkTextDirection mDirection;
+ const GdkRectangle& mGDKRect;
+ const GdkRectangle& mGDKClip;
+};
+
+nsresult
+ThemeRenderer::DrawWithGDK(GdkDrawable * drawable, gint offsetX,
+ gint offsetY, GdkRectangle * clipRects, uint32_t numClipRects)
+{
+ GdkRectangle gdk_rect = mGDKRect;
+ gdk_rect.x += offsetX;
+ gdk_rect.y += offsetY;
+
+ GdkRectangle gdk_clip = mGDKClip;
+ gdk_clip.x += offsetX;
+ gdk_clip.y += offsetY;
+
+ GdkRectangle surfaceRect;
+ surfaceRect.x = 0;
+ surfaceRect.y = 0;
+ gdk_drawable_get_size(drawable, &surfaceRect.width, &surfaceRect.height);
+ gdk_rectangle_intersect(&gdk_clip, &surfaceRect, &gdk_clip);
+
+ NS_ASSERTION(numClipRects == 0, "We don't support clipping!!!");
+ moz_gtk_widget_paint(mGTKWidgetType, drawable, &gdk_rect, &gdk_clip,
+ &mState, mFlags, mDirection);
+
+ return NS_OK;
+}
+#else
+class SystemCairoClipper : public ClipExporter {
+public:
+ explicit SystemCairoClipper(cairo_t* aContext) : mContext(aContext)
+ {
+ }
+
+ void
+ BeginClip(const Matrix& aTransform) override
+ {
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(aTransform, mat);
+ cairo_set_matrix(mContext, &mat);
+
+ cairo_new_path(mContext);
+ }
+
+ void
+ MoveTo(const Point &aPoint) override
+ {
+ cairo_move_to(mContext, aPoint.x, aPoint.y);
+ mCurrentPoint = aPoint;
+ }
+
+ void
+ LineTo(const Point &aPoint) override
+ {
+ cairo_line_to(mContext, aPoint.x, aPoint.y);
+ mCurrentPoint = aPoint;
+ }
+
+ void
+ BezierTo(const Point &aCP1, const Point &aCP2, const Point &aCP3) override
+ {
+ cairo_curve_to(mContext, aCP1.x, aCP1.y, aCP2.x, aCP2.y, aCP3.x, aCP3.y);
+ mCurrentPoint = aCP3;
+ }
+
+ void
+ QuadraticBezierTo(const Point &aCP1, const Point &aCP2) override
+ {
+ Point CP0 = CurrentPoint();
+ Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
+ Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
+ Point CP3 = aCP2;
+ cairo_curve_to(mContext, CP1.x, CP1.y, CP2.x, CP2.y, CP3.x, CP3.y);
+ mCurrentPoint = aCP2;
+ }
+
+ void
+ Arc(const Point &aOrigin, float aRadius, float aStartAngle, float aEndAngle,
+ bool aAntiClockwise) override
+ {
+ ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
+ aAntiClockwise);
+ }
+
+ void
+ Close() override
+ {
+ cairo_close_path(mContext);
+ }
+
+ void
+ EndClip() override
+ {
+ cairo_clip(mContext);
+ }
+
+ Point
+ CurrentPoint() const override
+ {
+ return mCurrentPoint;
+ }
+
+private:
+ cairo_t* mContext;
+ Point mCurrentPoint;
+};
+
+static void
+DrawThemeWithCairo(gfxContext* aContext, DrawTarget* aDrawTarget,
+ GtkWidgetState aState, WidgetNodeType aGTKWidgetType,
+ gint aFlags, GtkTextDirection aDirection, gint aScaleFactor,
+ bool aSnapped, const Point& aDrawOrigin, const nsIntSize& aDrawSize,
+ GdkRectangle& aGDKRect, nsITheme::Transparency aTransparency)
+{
+ Point drawOffset;
+ Matrix transform;
+ if (!aSnapped) {
+ // If we are not snapped, we depend on the DT for translation.
+ drawOffset = aDrawOrigin;
+ transform = aDrawTarget->GetTransform().PreTranslate(aDrawOrigin);
+ } else {
+ // Otherwise, we only need to take the device offset into account.
+ drawOffset = aDrawOrigin - aContext->GetDeviceOffset();
+ transform = Matrix::Translation(drawOffset);
+ }
+
+ if (aScaleFactor != 1)
+ transform.PreScale(aScaleFactor, aScaleFactor);
+
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(transform, mat);
+
+ nsIntSize clipSize((aDrawSize.width + aScaleFactor - 1) / aScaleFactor,
+ (aDrawSize.height + aScaleFactor - 1) / aScaleFactor);
+
+#ifndef MOZ_TREE_CAIRO
+ // Directly use the Cairo draw target to render the widget if using system Cairo everywhere.
+ BorrowedCairoContext borrowCairo(aDrawTarget);
+ if (borrowCairo.mCairo) {
+ cairo_set_matrix(borrowCairo.mCairo, &mat);
+
+ cairo_new_path(borrowCairo.mCairo);
+ cairo_rectangle(borrowCairo.mCairo, 0, 0, clipSize.width, clipSize.height);
+ cairo_clip(borrowCairo.mCairo);
+
+ moz_gtk_widget_paint(aGTKWidgetType, borrowCairo.mCairo, &aGDKRect, &aState, aFlags, aDirection);
+
+ borrowCairo.Finish();
+ return;
+ }
+#endif
+
+ // A direct Cairo draw target is not available, so we need to create a temporary one.
+#if defined(MOZ_X11) && defined(CAIRO_HAS_XLIB_SURFACE)
+ // If using a Cairo xlib surface, then try to reuse it.
+ BorrowedXlibDrawable borrow(aDrawTarget);
+ if (borrow.GetDrawable()) {
+ nsIntSize size = borrow.GetSize();
+ cairo_surface_t* surf = nullptr;
+ // Check if the surface is using XRender.
+#ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
+ if (borrow.GetXRenderFormat()) {
+ surf = cairo_xlib_surface_create_with_xrender_format(
+ borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetScreen(),
+ borrow.GetXRenderFormat(), size.width, size.height);
+ } else {
+#else
+ if (! borrow.GetXRenderFormat()) {
+#endif
+ surf = cairo_xlib_surface_create(
+ borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetVisual(),
+ size.width, size.height);
+ }
+ if (!NS_WARN_IF(!surf)) {
+ Point offset = borrow.GetOffset();
+ if (offset != Point()) {
+ cairo_surface_set_device_offset(surf, offset.x, offset.y);
+ }
+ cairo_t* cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
+ aContext->ExportClip(*clipper);
+
+ cairo_set_matrix(cr, &mat);
+
+ cairo_new_path(cr);
+ cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
+ cairo_clip(cr);
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection);
+
+ cairo_destroy(cr);
+ }
+ cairo_surface_destroy(surf);
+ }
+ borrow.Finish();
+ return;
+ }
+#endif
+
+ // Check if the widget requires complex masking that must be composited.
+ // Try to directly write to the draw target's pixels if possible.
+ uint8_t* data;
+ nsIntSize size;
+ int32_t stride;
+ SurfaceFormat format;
+ IntPoint origin;
+ if (aDrawTarget->LockBits(&data, &size, &stride, &format, &origin)) {
+ // Create a Cairo image surface context the device rectangle.
+ cairo_surface_t* surf =
+ cairo_image_surface_create_for_data(
+ data, GfxFormatToCairoFormat(format), size.width, size.height, stride);
+ if (!NS_WARN_IF(!surf)) {
+ if (origin != IntPoint()) {
+ cairo_surface_set_device_offset(surf, -origin.x, -origin.y);
+ }
+ cairo_t* cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
+ aContext->ExportClip(*clipper);
+
+ cairo_set_matrix(cr, &mat);
+
+ cairo_new_path(cr);
+ cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
+ cairo_clip(cr);
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection);
+
+ cairo_destroy(cr);
+ }
+ cairo_surface_destroy(surf);
+ }
+ aDrawTarget->ReleaseBits(data);
+ } else {
+ // If the widget has any transparency, make sure to choose an alpha format.
+ format = aTransparency != nsITheme::eOpaque ? SurfaceFormat::B8G8R8A8 : aDrawTarget->GetFormat();
+ // Create a temporary data surface to render the widget into.
+ RefPtr<DataSourceSurface> dataSurface =
+ Factory::CreateDataSourceSurface(aDrawSize, format, aTransparency != nsITheme::eOpaque);
+ DataSourceSurface::MappedSurface map;
+ if (!NS_WARN_IF(!(dataSurface && dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)))) {
+ // Create a Cairo image surface wrapping the data surface.
+ cairo_surface_t* surf =
+ cairo_image_surface_create_for_data(map.mData, GfxFormatToCairoFormat(format),
+ aDrawSize.width, aDrawSize.height, map.mStride);
+ cairo_t* cr = nullptr;
+ if (!NS_WARN_IF(!surf)) {
+ cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ if (aScaleFactor != 1) {
+ cairo_scale(cr, aScaleFactor, aScaleFactor);
+ }
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection);
+ }
+ }
+
+ // Unmap the surface before using it as a source
+ dataSurface->Unmap();
+
+ if (cr) {
+ if (!aSnapped || aTransparency != nsITheme::eOpaque) {
+ // The widget either needs to be masked or has transparency, so use the slower drawing path.
+ aDrawTarget->DrawSurface(dataSurface,
+ Rect(aSnapped ? drawOffset - aDrawTarget->GetTransform().GetTranslation() : drawOffset,
+ Size(aDrawSize)),
+ Rect(0, 0, aDrawSize.width, aDrawSize.height));
+ } else {
+ // The widget is a simple opaque rectangle, so just copy it out.
+ aDrawTarget->CopySurface(dataSurface,
+ IntRect(0, 0, aDrawSize.width, aDrawSize.height),
+ TruncatedToInt(drawOffset));
+ }
+
+ cairo_destroy(cr);
+ }
+
+ if (surf) {
+ cairo_surface_destroy(surf);
+ }
+ }
+ }
+}
+#endif
+
+bool
+nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIntMargin* aExtra)
+{
+ *aExtra = nsIntMargin(0,0,0,0);
+ // Allow an extra one pixel above and below the thumb for certain
+ // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least);
+ // We modify the frame's overflow area. See bug 297508.
+ switch (aWidgetType) {
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ aExtra->top = aExtra->bottom = 1;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ aExtra->left = aExtra->right = 1;
+ break;
+
+ // Include the indicator spacing (the padding around the control).
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ {
+ gint indicator_size, indicator_spacing;
+
+ if (aWidgetType == NS_THEME_CHECKBOX) {
+ moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing);
+ } else {
+ moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
+ }
+
+ aExtra->top = indicator_spacing;
+ aExtra->right = indicator_spacing;
+ aExtra->bottom = indicator_spacing;
+ aExtra->left = indicator_spacing;
+ break;
+ }
+ case NS_THEME_BUTTON :
+ {
+ if (IsDefaultButton(aFrame)) {
+ // Some themes draw a default indicator outside the widget,
+ // include that in overflow
+ gint top, left, bottom, right;
+ moz_gtk_button_get_default_overflow(&top, &left, &bottom, &right);
+ aExtra->top = top;
+ aExtra->right = right;
+ aExtra->bottom = bottom;
+ aExtra->left = left;
+ break;
+ }
+ return false;
+ }
+ case NS_THEME_FOCUS_OUTLINE:
+ {
+ moz_gtk_get_focus_outline_size(&aExtra->left, &aExtra->top);
+ aExtra->right = aExtra->left;
+ aExtra->bottom = aExtra->top;
+ break;
+ }
+ case NS_THEME_TAB :
+ {
+ if (!IsSelectedTab(aFrame))
+ return false;
+
+ gint gap_height = moz_gtk_get_tab_thickness(IsBottomTab(aFrame) ?
+ MOZ_GTK_TAB_BOTTOM : MOZ_GTK_TAB_TOP);
+ if (!gap_height)
+ return false;
+
+ int32_t extra = gap_height - GetTabMarginPixels(aFrame);
+ if (extra <= 0)
+ return false;
+
+ if (IsBottomTab(aFrame)) {
+ aExtra->top = extra;
+ } else {
+ aExtra->bottom = extra;
+ }
+ return false;
+ }
+ default:
+ return false;
+ }
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ aExtra->top *= scale;
+ aExtra->right *= scale;
+ aExtra->bottom *= scale;
+ aExtra->left *= scale;
+ return true;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ GtkWidgetState state;
+ WidgetNodeType gtkWidgetType;
+ GtkTextDirection direction = GetTextDirection(aFrame);
+ gint flags;
+ if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, &state,
+ &flags))
+ return NS_OK;
+
+ gfxContext* ctx = aContext->ThebesContext();
+ nsPresContext *presContext = aFrame->PresContext();
+
+ gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
+ gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
+ gint scaleFactor = nsScreenGtk::GetGtkMonitorScaleFactor();
+
+ // Align to device pixels where sensible
+ // to provide crisper and faster drawing.
+ // Don't snap if it's a non-unit scale factor. We're going to have to take
+ // slow paths then in any case.
+ bool snapped = ctx->UserToDevicePixelSnapped(rect);
+ if (snapped) {
+ // Leave rect in device coords but make dirtyRect consistent.
+ dirtyRect = ctx->UserToDevice(dirtyRect);
+ }
+
+ // Translate the dirty rect so that it is wrt the widget top-left.
+ dirtyRect.MoveBy(-rect.TopLeft());
+ // Round out the dirty rect to gdk pixels to ensure that gtk draws
+ // enough pixels for interpolation to device pixels.
+ dirtyRect.RoundOut();
+
+ // GTK themes can only draw an integer number of pixels
+ // (even when not snapped).
+ nsIntRect widgetRect(0, 0, NS_lround(rect.Width()), NS_lround(rect.Height()));
+ nsIntRect overflowRect(widgetRect);
+ nsIntMargin extraSize;
+ if (GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize)) {
+ overflowRect.Inflate(extraSize);
+ }
+
+ // This is the rectangle that will actually be drawn, in gdk pixels
+ nsIntRect drawingRect(int32_t(dirtyRect.X()),
+ int32_t(dirtyRect.Y()),
+ int32_t(dirtyRect.Width()),
+ int32_t(dirtyRect.Height()));
+ if (widgetRect.IsEmpty()
+ || !drawingRect.IntersectRect(overflowRect, drawingRect))
+ return NS_OK;
+
+ NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType),
+ "Trying to render an unsafe widget!");
+
+ bool safeState = IsWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
+ if (!safeState) {
+ gLastGdkError = 0;
+ gdk_error_trap_push ();
+ }
+
+ Transparency transparency = GetWidgetTransparency(aFrame, aWidgetType);
+
+ // gdk rectangles are wrt the drawing rect.
+ GdkRectangle gdk_rect = {-drawingRect.x/scaleFactor,
+ -drawingRect.y/scaleFactor,
+ widgetRect.width/scaleFactor,
+ widgetRect.height/scaleFactor};
+
+ // translate everything so (0,0) is the top left of the drawingRect
+ gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft();
+
+#if (MOZ_WIDGET_GTK == 2)
+ gfxContextAutoSaveRestore autoSR(ctx);
+ gfxMatrix matrix;
+ if (!snapped) { // else rects are in device coords
+ matrix = ctx->CurrentMatrix();
+ }
+ matrix.Translate(origin);
+ matrix.Scale(scaleFactor, scaleFactor); // Draw in GDK coords
+ ctx->SetMatrix(matrix);
+
+ // The gdk_clip is just advisory here, meaning "you don't
+ // need to draw outside this rect if you don't feel like it!"
+ GdkRectangle gdk_clip = {0, 0, drawingRect.width, drawingRect.height};
+
+ ThemeRenderer renderer(state, gtkWidgetType, flags, direction,
+ gdk_rect, gdk_clip);
+
+ // Some themes (e.g. Clearlooks) just don't clip properly to any
+ // clip rect we provide, so we cannot advertise support for clipping within
+ // the widget bounds.
+ uint32_t rendererFlags = 0;
+ if (transparency == eOpaque) {
+ rendererFlags |= gfxGdkNativeRenderer::DRAW_IS_OPAQUE;
+ }
+
+ // GtkStyles (used by the widget drawing backend) are created for a
+ // particular colormap/visual.
+ GdkColormap* colormap = moz_gtk_widget_get_colormap();
+
+ renderer.Draw(ctx, drawingRect.Size(), rendererFlags, colormap);
+#else
+ DrawThemeWithCairo(ctx, aContext->GetDrawTarget(),
+ state, gtkWidgetType, flags, direction, scaleFactor,
+ snapped, ToPoint(origin), drawingRect.Size(),
+ gdk_rect, transparency);
+#endif
+
+ if (!safeState) {
+ gdk_flush();
+ gLastGdkError = gdk_error_trap_pop ();
+
+ if (gLastGdkError) {
+#ifdef DEBUG
+ printf("GTK theme failed for widget type %d, error was %d, state was "
+ "[active=%d,focused=%d,inHover=%d,disabled=%d]\n",
+ aWidgetType, gLastGdkError, state.active, state.focused,
+ state.inHover, state.disabled);
+#endif
+ NS_WARNING("GTK theme failed; disabling unsafe widget");
+ SetWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType);
+ // force refresh of the window, because the widget was not
+ // successfully drawn it must be redrawn using the default look
+ RefreshWidgetWindow(aFrame);
+ } else {
+ SetWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
+ }
+ }
+
+ // Indeterminate progress bar are animated.
+ if (gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
+ gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
+ NS_WARNING("unable to animate widget!");
+ }
+ }
+
+ return NS_OK;
+}
+
+WidgetNodeType
+nsNativeThemeGTK::NativeThemeToGtkTheme(uint8_t aWidgetType, nsIFrame* aFrame)
+{
+ WidgetNodeType gtkWidgetType;
+ gint unusedFlags;
+
+ if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
+ &unusedFlags))
+ {
+ MOZ_ASSERT_UNREACHABLE("Unknown native widget to gtk widget mapping");
+ return MOZ_GTK_WINDOW;
+ }
+ return gtkWidgetType;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
+ uint8_t aWidgetType, nsIntMargin* aResult)
+{
+ GtkTextDirection direction = GetTextDirection(aFrame);
+ aResult->top = aResult->left = aResult->right = aResult->bottom = 0;
+ switch (aWidgetType) {
+ case NS_THEME_TOOLBOX:
+ // gtk has no toolbox equivalent. So, although we map toolbox to
+ // gtk's 'toolbar' for purposes of painting the widget background,
+ // we don't use the toolbar border for toolbox.
+ break;
+ case NS_THEME_DUALBUTTON:
+ // TOOLBAR_DUAL_BUTTON is an interesting case. We want a border to draw
+ // around the entire button + dropdown, and also an inner border if you're
+ // over the button part. But, we want the inner button to be right up
+ // against the edge of the outer button so that the borders overlap.
+ // To make this happen, we draw a button border for the outer button,
+ // but don't reserve any space for it.
+ break;
+ case NS_THEME_TAB:
+ {
+ WidgetNodeType gtkWidgetType;
+ gint flags;
+
+ if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
+ &flags))
+ return NS_OK;
+
+ moz_gtk_get_tab_border(&aResult->left, &aResult->top,
+ &aResult->right, &aResult->bottom, direction,
+ (GtkTabFlags)flags, gtkWidgetType);
+ }
+ break;
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ // For regular menuitems, we will be using GetWidgetPadding instead of
+ // GetWidgetBorder to pad up the widget's internals; other menuitems
+ // will need to fall through and use the default case as before.
+ if (IsRegularMenuItem(aFrame))
+ break;
+ MOZ_FALLTHROUGH;
+ default:
+ {
+ WidgetNodeType gtkWidgetType;
+ gint unusedFlags;
+ if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
+ &unusedFlags)) {
+ moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
+ &aResult->right, &aResult->bottom, direction,
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML));
+ }
+ }
+ }
+
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ aResult->top *= scale;
+ aResult->right *= scale;
+ aResult->bottom *= scale;
+ aResult->left *= scale;
+ return NS_OK;
+}
+
+bool
+nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON_FOCUS:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_DUALBUTTON:
+ case NS_THEME_TAB_SCROLL_ARROW_BACK:
+ case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_TOOLBARBUTTON_DROPDOWN:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_BUTTON_ARROW_NEXT:
+ case NS_THEME_BUTTON_ARROW_PREVIOUS:
+ case NS_THEME_RANGE_THUMB:
+ // 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;
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ {
+ // Menubar and menulist have their padding specified in CSS.
+ if (!IsRegularMenuItem(aFrame))
+ return false;
+
+ aResult->SizeTo(0, 0, 0, 0);
+ WidgetNodeType gtkWidgetType;
+ if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
+ nullptr)) {
+ moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
+ &aResult->right, &aResult->bottom, GetTextDirection(aFrame),
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML));
+ }
+
+ gint horizontal_padding;
+
+ if (aWidgetType == NS_THEME_MENUITEM)
+ moz_gtk_menuitem_get_horizontal_padding(&horizontal_padding);
+ else
+ moz_gtk_checkmenuitem_get_horizontal_padding(&horizontal_padding);
+
+ aResult->left += horizontal_padding;
+ aResult->right += horizontal_padding;
+
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ aResult->top *= scale;
+ aResult->right *= scale;
+ aResult->bottom *= scale;
+ aResult->left *= scale;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ nsRect* aOverflowRect)
+{
+ nsIntMargin extraSize;
+ if (!GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize))
+ return false;
+
+ int32_t p2a = aContext->AppUnitsPerDevPixel();
+ nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
+ NSIntPixelsToAppUnits(extraSize.right, p2a),
+ NSIntPixelsToAppUnits(extraSize.bottom, p2a),
+ NSIntPixelsToAppUnits(extraSize.left, p2a));
+
+ aOverflowRect->Inflate(m);
+ return true;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable)
+{
+ aResult->width = aResult->height = 0;
+ *aIsOverridable = true;
+
+ switch (aWidgetType) {
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ moz_gtk_get_widget_min_size(MOZ_GTK_SCROLLBAR_BUTTON,
+ &(aResult->width), &(aResult->height));
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ aResult->width = metrics.slider_width;
+ aResult->height = metrics.stepper_size;
+ }
+
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ moz_gtk_get_widget_min_size(MOZ_GTK_SCROLLBAR_BUTTON,
+ &(aResult->width), &(aResult->height));
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ aResult->width = metrics.stepper_size;
+ aResult->height = metrics.slider_width;
+ }
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_SPLITTER:
+ {
+ gint metrics;
+ if (IsHorizontal(aFrame)) {
+ moz_gtk_splitter_get_metrics(GTK_ORIENTATION_HORIZONTAL, &metrics);
+ aResult->width = metrics;
+ aResult->height = 0;
+ } else {
+ moz_gtk_splitter_get_metrics(GTK_ORIENTATION_VERTICAL, &metrics);
+ aResult->width = 0;
+ aResult->height = metrics;
+ }
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ {
+ /* While we enforce a minimum size for the thumb, this is ignored
+ * for the some scrollbars if buttons are hidden (bug 513006) because
+ * the thumb isn't a direct child of the scrollbar, unlike the buttons
+ * or track. So add a minimum size to the track as well to prevent a
+ * 0-width scrollbar. */
+ if (gtk_check_version(3,20,0) == nullptr) {
+ // Thumb min dimensions to start with
+ WidgetNodeType thumbType = aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ?
+ MOZ_GTK_SCROLLBAR_THUMB_VERTICAL : MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ moz_gtk_get_widget_min_size(thumbType, &(aResult->width), &(aResult->height));
+
+ // Add scrollbar's borders
+ nsIntMargin border;
+ nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
+ aFrame, aWidgetType, &border);
+ aResult->width += border.left + border.right;
+ aResult->height += border.top + border.bottom;
+
+ // Add track's borders
+ uint8_t trackType = aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ?
+ NS_THEME_SCROLLBARTRACK_VERTICAL : NS_THEME_SCROLLBARTRACK_HORIZONTAL;
+ nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
+ aFrame, trackType, &border);
+ aResult->width += border.left + border.right;
+ aResult->height += border.top + border.bottom;
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ // Require room for the slider in the track if we don't have buttons.
+ bool hasScrollbarButtons = moz_gtk_has_scrollbar_buttons();
+
+ if (aWidgetType == NS_THEME_SCROLLBAR_VERTICAL) {
+ aResult->width = metrics.slider_width + 2 * metrics.trough_border;
+ if (!hasScrollbarButtons)
+ aResult->height = metrics.min_slider_size + 2 * metrics.trough_border;
+ } else {
+ aResult->height = metrics.slider_width + 2 * metrics.trough_border;
+ if (!hasScrollbarButtons)
+ aResult->width = metrics.min_slider_size + 2 * metrics.trough_border;
+ }
+ *aIsOverridable = false;
+ }
+
+ }
+ break;
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ moz_gtk_get_widget_min_size(NativeThemeToGtkTheme(aWidgetType, aFrame),
+ &(aResult->width), &(aResult->height));
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ if (aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL) {
+ aResult->width = metrics.slider_width;
+ aResult->height = metrics.min_slider_size;
+ } else {
+ aResult->height = metrics.slider_width;
+ aResult->width = metrics.min_slider_size;
+ }
+ }
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_RANGE_THUMB:
+ {
+ gint thumb_length, thumb_height;
+
+ if (IsRangeHorizontal(aFrame)) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_length, &thumb_height);
+ } else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height, &thumb_length);
+ }
+ aResult->width = thumb_length;
+ aResult->height = thumb_height;
+
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_RANGE:
+ {
+ gint scale_width, scale_height;
+
+ moz_gtk_get_scale_metrics(IsRangeHorizontal(aFrame) ?
+ GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL,
+ &scale_width, &scale_height);
+ aResult->width = scale_width;
+ aResult->height = scale_height;
+
+ *aIsOverridable = true;
+ }
+ break;
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ {
+ gint thumb_length, thumb_height;
+
+ if (aWidgetType == NS_THEME_SCALETHUMB_VERTICAL) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_length, &thumb_height);
+ aResult->width = thumb_height;
+ aResult->height = thumb_length;
+ } else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_length, &thumb_height);
+ aResult->width = thumb_length;
+ aResult->height = thumb_height;
+ }
+
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_TAB_SCROLL_ARROW_BACK:
+ case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
+ {
+ moz_gtk_get_tab_scroll_arrow_size(&aResult->width, &aResult->height);
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_MENULIST_BUTTON:
+ {
+ moz_gtk_get_combo_box_entry_button_size(&aResult->width,
+ &aResult->height);
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_MENUSEPARATOR:
+ {
+ gint separator_height;
+
+ moz_gtk_get_menu_separator_height(&separator_height);
+ aResult->height = separator_height;
+
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ {
+ gint indicator_size, indicator_spacing;
+
+ if (aWidgetType == NS_THEME_CHECKBOX) {
+ moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing);
+ } else {
+ moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
+ }
+
+ // Include space for the indicator and the padding around it.
+ aResult->width = indicator_size;
+ aResult->height = indicator_size;
+ }
+ break;
+ case NS_THEME_TOOLBARBUTTON_DROPDOWN:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_BUTTON_ARROW_NEXT:
+ case NS_THEME_BUTTON_ARROW_PREVIOUS:
+ {
+ moz_gtk_get_arrow_size(MOZ_GTK_TOOLBARBUTTON_ARROW,
+ &aResult->width, &aResult->height);
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_CHECKBOX_CONTAINER:
+ case NS_THEME_RADIO_CONTAINER:
+ case NS_THEME_CHECKBOX_LABEL:
+ case NS_THEME_RADIO_LABEL:
+ case NS_THEME_BUTTON:
+ case NS_THEME_MENULIST:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_TREEHEADERCELL:
+ {
+ if (aWidgetType == NS_THEME_MENULIST) {
+ // Include the arrow size.
+ moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN,
+ &aResult->width, &aResult->height);
+ }
+ // else the minimum size is missing consideration of container
+ // descendants; the value returned here will not be helpful, but the
+ // box model may consider border and padding with child minimum sizes.
+
+ nsIntMargin border;
+ nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
+ aFrame, aWidgetType, &border);
+ aResult->width += border.left + border.right;
+ aResult->height += border.top + border.bottom;
+ }
+ break;
+#if (MOZ_WIDGET_GTK == 3)
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ {
+ moz_gtk_get_entry_min_height(&aResult->height);
+ }
+ break;
+#endif
+ case NS_THEME_SEPARATOR:
+ {
+ gint separator_width;
+
+ moz_gtk_get_toolbar_separator_width(&separator_width);
+
+ aResult->width = separator_width;
+ }
+ break;
+ case NS_THEME_SPINNER:
+ // hard code these sizes
+ aResult->width = 14;
+ aResult->height = 26;
+ break;
+ case NS_THEME_TREEHEADERSORTARROW:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ // hard code these sizes
+ aResult->width = 14;
+ aResult->height = 13;
+ break;
+ case NS_THEME_RESIZER:
+ // same as Windows to make our lives easier
+ aResult->width = aResult->height = 15;
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_TREETWISTY:
+ case NS_THEME_TREETWISTYOPEN:
+ {
+ gint expander_size;
+
+ moz_gtk_get_treeview_expander_size(&expander_size);
+ aResult->width = aResult->height = expander_size;
+ *aIsOverridable = false;
+ }
+ break;
+ }
+
+ *aResult = *aResult * nsScreenGtk::GetGtkMonitorScaleFactor();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::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_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_MENUBAR ||
+ aWidgetType == NS_THEME_MENUPOPUP ||
+ aWidgetType == NS_THEME_TOOLTIP ||
+ aWidgetType == NS_THEME_MENUSEPARATOR ||
+ aWidgetType == NS_THEME_WINDOW ||
+ aWidgetType == NS_THEME_DIALOG) {
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ if ((aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL ||
+ aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) &&
+ aAttribute == nsGkAtoms::active) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ if ((aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) &&
+ (aAttribute == nsGkAtoms::curpos ||
+ aAttribute == nsGkAtoms::maxpos)) {
+ // If 'curpos' has changed and we are passed its old value, we can
+ // determine whether the button's enablement actually needs to change.
+ if (aAttribute == nsGkAtoms::curpos && aOldValue) {
+ int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
+ int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 0);
+ nsAutoString str;
+ aOldValue->ToString(str);
+ nsresult err;
+ int32_t oldCurpos = str.ToInteger(&err);
+ if (str.IsEmpty() || NS_FAILED(err)) {
+ *aShouldRepaint = true;
+ } else {
+ bool disabledBefore = ShouldScrollbarButtonBeDisabled(oldCurpos, maxpos, aWidgetType);
+ bool disabledNow = ShouldScrollbarButtonBeDisabled(curpos, maxpos, aWidgetType);
+ *aShouldRepaint = (disabledBefore != disabledNow);
+ }
+ } else {
+ *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::focused ||
+ aAttribute == nsGkAtoms::readonly ||
+ aAttribute == nsGkAtoms::_default ||
+ aAttribute == nsGkAtoms::menuactive ||
+ aAttribute == nsGkAtoms::open ||
+ aAttribute == nsGkAtoms::parentfocused)
+ *aShouldRepaint = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::ThemeChanged()
+{
+ memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType))
+ return false;
+
+ switch (aWidgetType) {
+ // Combobox dropdowns don't support native theming in vertical mode.
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXT:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ MOZ_FALLTHROUGH;
+
+ case NS_THEME_BUTTON:
+ case NS_THEME_BUTTON_FOCUS:
+ case NS_THEME_RADIO:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_TOOLBOX: // N/A
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_DUALBUTTON: // so we can override the border with 0
+ case NS_THEME_TOOLBARBUTTON_DROPDOWN:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_BUTTON_ARROW_NEXT:
+ case NS_THEME_BUTTON_ARROW_PREVIOUS:
+ case NS_THEME_SEPARATOR:
+ case NS_THEME_TOOLBARGRIPPER:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_RESIZER:
+ case NS_THEME_LISTBOX:
+ // case NS_THEME_LISTITEM:
+ case NS_THEME_TREEVIEW:
+ // case NS_THEME_TREEITEM:
+ case NS_THEME_TREETWISTY:
+ // case NS_THEME_TREELINE:
+ // case NS_THEME_TREEHEADER:
+ case NS_THEME_TREEHEADERCELL:
+ case NS_THEME_TREEHEADERSORTARROW:
+ case NS_THEME_TREETWISTYOPEN:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TAB:
+ // case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_TAB_SCROLL_ARROW_BACK:
+ case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_SPINNER:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_SPINNER_TEXTFIELD:
+ // case NS_THEME_SCROLLBAR: (n/a for gtk)
+ // case NS_THEME_SCROLLBAR_SMALL: (n/a for gtk)
+ 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_SCROLLBARTRACK_HORIZONTAL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_RANGE:
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ // case NS_THEME_SCALETHUMBSTART:
+ // case NS_THEME_SCALETHUMBEND:
+ // case NS_THEME_SCALETHUMBTICK:
+ case NS_THEME_CHECKBOX_CONTAINER:
+ case NS_THEME_RADIO_CONTAINER:
+ case NS_THEME_CHECKBOX_LABEL:
+ case NS_THEME_RADIO_LABEL:
+ case NS_THEME_MENUBAR:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ case NS_THEME_SPLITTER:
+ case NS_THEME_WINDOW:
+ case NS_THEME_DIALOG:
+#if (MOZ_WIDGET_GTK == 3)
+ case NS_THEME_GTK_INFO_BAR:
+#endif
+ return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
+
+ case NS_THEME_MENULIST_BUTTON:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ // "Native" dropdown buttons cause padding and margin problems, but only
+ // in HTML so allow them in XUL.
+ return (!aFrame || IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) &&
+ !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
+
+ case NS_THEME_FOCUS_OUTLINE:
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeGTK::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_RANGE_THUMB ||
+ aWidgetType == NS_THEME_CHECKBOX ||
+ aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK ||
+ aWidgetType == NS_THEME_TAB_SCROLL_ARROW_FORWARD ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_UP ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_DOWN ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_NEXT ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS)
+ return false;
+ return true;
+}
+
+bool
+nsNativeThemeGTK::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
+{
+ if (aWidgetType == NS_THEME_MENULIST ||
+ aWidgetType == NS_THEME_BUTTON ||
+ aWidgetType == NS_THEME_TREEHEADERCELL)
+ return true;
+
+ return false;
+}
+
+bool
+nsNativeThemeGTK::ThemeNeedsComboboxDropmarker()
+{
+ return false;
+}
+
+nsITheme::Transparency
+nsNativeThemeGTK::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ // These widgets always draw a default background.
+#if (MOZ_WIDGET_GTK == 2)
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_MENUBAR:
+#endif
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_WINDOW:
+ case NS_THEME_DIALOG:
+ return eOpaque;
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+#if (MOZ_WIDGET_GTK == 3)
+ // Make scrollbar tracks opaque on the window's scroll frame to prevent
+ // leaf layers from overlapping. See bug 1179780.
+ if (!(CheckBooleanAttr(aFrame, nsGkAtoms::root_) &&
+ aFrame->PresContext()->IsRootContentDocument() &&
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)))
+ return eTransparent;
+#endif
+ return eOpaque;
+ // Tooltips use gtk_paint_flat_box() on Gtk2
+ // but are shaped on Gtk3
+ case NS_THEME_TOOLTIP:
+#if (MOZ_WIDGET_GTK == 2)
+ return eOpaque;
+#else
+ return eTransparent;
+#endif
+ }
+
+ return eUnknownTransparency;
+}