Back to index

lightning-sunbird  0.9+nobinonly
nsNativeThemeGTK.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 2002
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *  Brian Ryner <bryner@brianryner.com>  (Original Author)
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsNativeThemeGTK.h"
00040 #include "nsThemeConstants.h"
00041 #include "nsDrawingSurfaceGTK.h"
00042 #include "nsDeviceContextGTK.h"
00043 #include "gtkdrawing.h"
00044 
00045 #include "nsIObserverService.h"
00046 #include "nsIServiceManager.h"
00047 #include "nsIFrame.h"
00048 #include "nsIPresShell.h"
00049 #include "nsIDocument.h"
00050 #include "nsIContent.h"
00051 #include "nsIEventStateManager.h"
00052 #include "nsIViewManager.h"
00053 #include "nsINameSpaceManager.h"
00054 #include "nsILookAndFeel.h"
00055 #include "nsIDeviceContext.h"
00056 #include "nsTransform2D.h"
00057 #include "nsIMenuFrame.h"
00058 #include "nsIMenuParent.h"
00059 #include "prlink.h"
00060 
00061 #include <gdk/gdkprivate.h>
00062 
00063 #include <gdk/gdkx.h>
00064 
00065 NS_IMPL_ISUPPORTS2(nsNativeThemeGTK, nsITheme, nsIObserver)
00066 
00067 static int gLastXError;
00068 
00069 nsNativeThemeGTK::nsNativeThemeGTK()
00070 {
00071   if (moz_gtk_init() != MOZ_GTK_SUCCESS) {
00072     memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes));
00073     return;
00074   }
00075 
00076   // We have to call moz_gtk_shutdown before the event loop stops running.
00077   nsCOMPtr<nsIObserverService> obsServ =
00078     do_GetService("@mozilla.org/observer-service;1");
00079   obsServ->AddObserver(this, "xpcom-shutdown", PR_FALSE);
00080 
00081   mInputCheckedAtom = do_GetAtom("_moz-input-checked");
00082   mInputAtom = do_GetAtom("input");
00083   mCurPosAtom = do_GetAtom("curpos");
00084   mMaxPosAtom = do_GetAtom("maxpos");
00085   mMenuActiveAtom = do_GetAtom("_moz-menuactive");
00086 
00087   memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
00088   memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
00089 
00090 #ifdef MOZ_WIDGET_GTK
00091   // Look up the symbol for gtk_style_get_prop_experimental
00092   PRLibrary* gtkLibrary;
00093   PRFuncPtr stylePropFunc = PR_FindFunctionSymbolAndLibrary("gtk_style_get_prop_experimental", &gtkLibrary);
00094   if (stylePropFunc) {
00095     moz_gtk_enable_style_props((style_prop_t) stylePropFunc);
00096     PR_UnloadLibrary(gtkLibrary);
00097   }
00098 #endif
00099 }
00100 
00101 nsNativeThemeGTK::~nsNativeThemeGTK() {
00102 }
00103 
00104 NS_IMETHODIMP
00105 nsNativeThemeGTK::Observe(nsISupports *aSubject, const char *aTopic,
00106                           const PRUnichar *aData)
00107 {
00108   if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
00109     moz_gtk_shutdown();
00110   } else {
00111     NS_NOTREACHED("unexpected topic");
00112     return NS_ERROR_UNEXPECTED;
00113   }
00114 
00115   return NS_OK;
00116 }
00117 
00118 void
00119 nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame)
00120 {
00121   nsIPresShell *shell = GetPresShell(aFrame);
00122   if (!shell)
00123     return;
00124 
00125   nsIViewManager* vm = shell->GetViewManager();
00126   if (!vm)
00127     return;
00128  
00129   vm->UpdateAllViews(NS_VMREFRESH_NO_SYNC);
00130 }
00131 
00132 static PRBool IsWidgetTypeDisabled(PRUint8* aDisabledVector, PRUint8 aWidgetType) {
00133   return aDisabledVector[aWidgetType >> 3] & (1 << (aWidgetType & 7));
00134 }
00135 
00136 static void SetWidgetTypeDisabled(PRUint8* aDisabledVector, PRUint8 aWidgetType) {
00137   aDisabledVector[aWidgetType >> 3] |= (1 << (aWidgetType & 7));
00138 }
00139 
00140 static inline PRUint16
00141 GetWidgetStateKey(PRUint8 aWidgetType, GtkWidgetState *aWidgetState)
00142 {
00143   return (aWidgetState->active |
00144           aWidgetState->focused << 1 |
00145           aWidgetState->inHover << 2 |
00146           aWidgetState->disabled << 3 |
00147           aWidgetState->isDefault << 4 |
00148           aWidgetType << 5);
00149 }
00150 
00151 static PRBool IsWidgetStateSafe(PRUint8* aSafeVector,
00152                                 PRUint8 aWidgetType,
00153                                 GtkWidgetState *aWidgetState)
00154 {
00155   PRUint8 key = GetWidgetStateKey(aWidgetType, aWidgetState);
00156   return aSafeVector[key >> 3] & (1 << (key & 7));
00157 }
00158 
00159 static void SetWidgetStateSafe(PRUint8 *aSafeVector,
00160                                PRUint8 aWidgetType,
00161                                GtkWidgetState *aWidgetState)
00162 {
00163   PRUint8 key = GetWidgetStateKey(aWidgetType, aWidgetState);
00164   aSafeVector[key >> 3] |= (1 << (key & 7));
00165 }
00166 
00167 PRBool
00168 nsNativeThemeGTK::GetGtkWidgetAndState(PRUint8 aWidgetType, nsIFrame* aFrame,
00169                                        GtkThemeWidgetType& aGtkWidgetType,
00170                                        GtkWidgetState* aState,
00171                                        gint* aWidgetFlags)
00172 {
00173   if (aState) {
00174     if (!aFrame) {
00175       // reset the entire struct to zero
00176       memset(aState, 0, sizeof(GtkWidgetState));
00177     } else {
00178 
00179       // for dropdown textfields, look at the parent frame (textbox or menulist)
00180       if (aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD)
00181         aFrame = aFrame->GetParent();
00182 
00183       // For XUL checkboxes and radio buttons, the state of the parent
00184       // determines our state.
00185       nsIFrame *stateFrame = aFrame;
00186       if (aFrame && ((aWidgetFlags && (aWidgetType == NS_THEME_CHECKBOX ||
00187                                        aWidgetType == NS_THEME_RADIO)) ||
00188                      aWidgetType == NS_THEME_CHECKBOX_LABEL ||
00189                      aWidgetType == NS_THEME_RADIO_LABEL)) {
00190 
00191         nsIAtom* atom = nsnull;
00192         nsIContent *content = aFrame->GetContent();
00193         if (content->IsContentOfType(nsIContent::eXUL)) {
00194           if (aWidgetType == NS_THEME_CHECKBOX_LABEL ||
00195               aWidgetType == NS_THEME_RADIO_LABEL) {
00196             // Adjust stateFrame so GetContentState finds the correct state.
00197             stateFrame = aFrame = aFrame->GetParent()->GetParent();
00198           } else {
00199             // GetContentState knows to look one frame up for radio/checkbox
00200             // widgets, so don't adjust stateFrame here.
00201             aFrame = aFrame->GetParent();
00202           }
00203         } else if (content->Tag() == mInputAtom) {
00204           atom = mInputCheckedAtom;
00205         }
00206 
00207         if (aWidgetFlags) {
00208           if (!atom) {
00209             atom = (aWidgetType == NS_THEME_CHECKBOX || aWidgetType == NS_THEME_CHECKBOX_LABEL) ? mCheckedAtom : mSelectedAtom;
00210           }
00211           *aWidgetFlags = CheckBooleanAttr(aFrame, atom);
00212         }
00213       }
00214 
00215       PRInt32 eventState = GetContentState(stateFrame, aWidgetType);
00216 
00217       aState->disabled = IsDisabled(aFrame);
00218       aState->active  = (eventState & NS_EVENT_STATE_ACTIVE) == NS_EVENT_STATE_ACTIVE;
00219       aState->focused = (eventState & NS_EVENT_STATE_FOCUS) == NS_EVENT_STATE_FOCUS;
00220       aState->inHover = (eventState & NS_EVENT_STATE_HOVER) == NS_EVENT_STATE_HOVER;
00221       aState->isDefault = FALSE; // XXX fix me
00222       aState->canDefault = FALSE; // XXX fix me
00223 
00224       // For these widget types, some element (either a child or parent)
00225       // actually has element focus, so we check the focused attribute
00226       // to see whether to draw in the focused state.
00227       if (aWidgetType == NS_THEME_TEXTFIELD ||
00228           aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD ||
00229           aWidgetType == NS_THEME_RADIO_CONTAINER ||
00230           aWidgetType == NS_THEME_RADIO_LABEL ||
00231           aWidgetType == NS_THEME_RADIO) {
00232         aState->focused = IsFocused(aFrame);
00233       }
00234 
00235       if (aWidgetType == NS_THEME_SCROLLBAR_THUMB_VERTICAL ||
00236           aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL) {
00237         // for scrollbars we need to go up two to go from the thumb to
00238         // the slider to the actual scrollbar object
00239         nsIFrame *tmpFrame = aFrame->GetParent()->GetParent();
00240 
00241         aState->curpos = CheckIntAttr(tmpFrame, mCurPosAtom);
00242         aState->maxpos = CheckIntAttr(tmpFrame, mMaxPosAtom);
00243       }
00244 
00245       // menu item state is determined by the attribute "_moz-menuactive",
00246       // and not by the mouse hovering (accessibility).  as a special case,
00247       // menus which are children of a menu bar are only marked as prelight
00248       // if they are open, not on normal hover.
00249 
00250       if (aWidgetType == NS_THEME_MENUITEM ||
00251           aWidgetType == NS_THEME_CHECKMENUITEM ||
00252           aWidgetType == NS_THEME_RADIOMENUITEM) {
00253         PRBool isTopLevel = PR_FALSE;
00254         nsIMenuFrame *menuFrame;
00255         CallQueryInterface(aFrame, &menuFrame);
00256 
00257         if (menuFrame) {
00258           nsIMenuParent *menuParent = menuFrame->GetMenuParent();
00259           if (menuParent)
00260             menuParent->IsMenuBar(isTopLevel);
00261         }
00262 
00263         if (isTopLevel) {
00264           PRBool isOpen;
00265           menuFrame->MenuIsOpen(isOpen);
00266           aState->inHover = isOpen;
00267         } else {
00268           aState->inHover = CheckBooleanAttr(aFrame, mMenuActiveAtom);
00269         }
00270 
00271         aState->active = FALSE;
00272         
00273         if (aWidgetType == NS_THEME_CHECKMENUITEM ||
00274             aWidgetType == NS_THEME_RADIOMENUITEM) {
00275           if (aFrame) {
00276             nsAutoString attr;
00277             nsresult res = aFrame->GetContent()->GetAttr(kNameSpaceID_None, mCheckedAtom, attr);
00278             if (res == NS_CONTENT_ATTR_NO_VALUE ||
00279                (res != NS_CONTENT_ATTR_NOT_THERE && attr.IsEmpty()))
00280               *aWidgetFlags = FALSE;
00281             else
00282               *aWidgetFlags = attr.EqualsIgnoreCase("true");
00283           } else {
00284             *aWidgetFlags = FALSE;
00285           }
00286         }
00287       }
00288     }
00289   }
00290 
00291   switch (aWidgetType) {
00292   case NS_THEME_BUTTON:
00293   case NS_THEME_TOOLBAR_BUTTON:
00294   case NS_THEME_TOOLBAR_DUAL_BUTTON:
00295     if (aWidgetFlags)
00296       *aWidgetFlags = (aWidgetType == NS_THEME_BUTTON) ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE;
00297     aGtkWidgetType = MOZ_GTK_BUTTON;
00298     break;
00299   case NS_THEME_CHECKBOX:
00300   case NS_THEME_RADIO:
00301     aGtkWidgetType = (aWidgetType == NS_THEME_RADIO) ? MOZ_GTK_RADIOBUTTON : MOZ_GTK_CHECKBUTTON;
00302     break;
00303   case NS_THEME_SCROLLBAR_BUTTON_UP:
00304   case NS_THEME_SCROLLBAR_BUTTON_DOWN:
00305   case NS_THEME_SCROLLBAR_BUTTON_LEFT:
00306   case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
00307     if (aWidgetFlags)
00308       *aWidgetFlags = GtkArrowType(aWidgetType - NS_THEME_SCROLLBAR_BUTTON_UP);
00309     aGtkWidgetType = MOZ_GTK_SCROLLBAR_BUTTON;
00310     break;
00311   case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
00312     aGtkWidgetType = MOZ_GTK_SCROLLBAR_TRACK_VERTICAL;
00313     break;
00314   case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
00315     aGtkWidgetType = MOZ_GTK_SCROLLBAR_TRACK_HORIZONTAL;
00316     break;
00317   case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
00318     aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL;
00319     break;
00320   case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
00321     aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
00322     break;
00323   case NS_THEME_TOOLBAR_GRIPPER:
00324     aGtkWidgetType = MOZ_GTK_GRIPPER;
00325     break;
00326   case NS_THEME_TEXTFIELD:
00327   case NS_THEME_DROPDOWN_TEXTFIELD:
00328     aGtkWidgetType = MOZ_GTK_ENTRY;
00329     break;
00330   case NS_THEME_DROPDOWN:
00331     aGtkWidgetType = MOZ_GTK_DROPDOWN;
00332     break;
00333   case NS_THEME_DROPDOWN_TEXT:
00334     return PR_FALSE; // nothing to do, but prevents the bg from being drawn
00335   case NS_THEME_DROPDOWN_BUTTON:
00336     aGtkWidgetType = MOZ_GTK_DROPDOWN_ARROW;
00337     break;
00338   case NS_THEME_CHECKBOX_CONTAINER:
00339     aGtkWidgetType = MOZ_GTK_CHECKBUTTON_CONTAINER;
00340     break;
00341   case NS_THEME_RADIO_CONTAINER:
00342     aGtkWidgetType = MOZ_GTK_RADIOBUTTON_CONTAINER;
00343     break;
00344   case NS_THEME_CHECKBOX_LABEL:
00345     aGtkWidgetType = MOZ_GTK_CHECKBUTTON_LABEL;
00346     break;
00347   case NS_THEME_RADIO_LABEL:
00348     aGtkWidgetType = MOZ_GTK_RADIOBUTTON_LABEL;
00349     break;
00350   case NS_THEME_TOOLBAR:
00351     aGtkWidgetType = MOZ_GTK_TOOLBAR;
00352     break;
00353   case NS_THEME_TOOLTIP:
00354     aGtkWidgetType = MOZ_GTK_TOOLTIP;
00355     break;
00356   case NS_THEME_STATUSBAR_PANEL:
00357     aGtkWidgetType = MOZ_GTK_FRAME;
00358     break;
00359   case NS_THEME_PROGRESSBAR:
00360   case NS_THEME_PROGRESSBAR_VERTICAL:
00361     aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
00362     break;
00363   case NS_THEME_PROGRESSBAR_CHUNK:
00364   case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
00365     aGtkWidgetType = MOZ_GTK_PROGRESS_CHUNK;
00366     break;
00367   case NS_THEME_TAB_PANELS:
00368     aGtkWidgetType = MOZ_GTK_TABPANELS;
00369     break;
00370   case NS_THEME_TAB:
00371   case NS_THEME_TAB_LEFT_EDGE:
00372   case NS_THEME_TAB_RIGHT_EDGE:
00373     {
00374       if (aWidgetFlags) {
00375         *aWidgetFlags = 0;
00376 
00377         if (aWidgetType == NS_THEME_TAB &&
00378             CheckBooleanAttr(aFrame, mSelectedAtom))
00379           *aWidgetFlags |= MOZ_GTK_TAB_SELECTED;
00380         else if (aWidgetType == NS_THEME_TAB_LEFT_EDGE)
00381           *aWidgetFlags |= MOZ_GTK_TAB_BEFORE_SELECTED;
00382 
00383         if (aFrame->GetContent()->HasAttr(kNameSpaceID_None, mFirstTabAtom))
00384           *aWidgetFlags |= MOZ_GTK_TAB_FIRST;
00385       }
00386 
00387       aGtkWidgetType = MOZ_GTK_TAB;
00388     }
00389     break;
00390   case NS_THEME_MENUBAR:
00391     aGtkWidgetType = MOZ_GTK_MENUBAR;
00392     break;
00393   case NS_THEME_MENUPOPUP:
00394     aGtkWidgetType = MOZ_GTK_MENUPOPUP;
00395     break;
00396   case NS_THEME_MENUITEM:
00397     aGtkWidgetType = MOZ_GTK_MENUITEM;
00398     break;
00399   case NS_THEME_CHECKMENUITEM:
00400     aGtkWidgetType = MOZ_GTK_CHECKMENUITEM;
00401     break;
00402   case NS_THEME_RADIOMENUITEM:
00403     aGtkWidgetType = MOZ_GTK_RADIOMENUITEM;
00404     break;
00405   case NS_THEME_WINDOW:
00406   case NS_THEME_DIALOG:
00407     aGtkWidgetType = MOZ_GTK_WINDOW;
00408     break;
00409   default:
00410     return PR_FALSE;
00411   }
00412 
00413   return PR_TRUE;
00414 }
00415 
00416 static int
00417 NativeThemeErrorHandler(Display* dpy, XErrorEvent* error) {
00418   gLastXError = error->error_code;
00419   return 0;
00420 }
00421 
00422 NS_IMETHODIMP
00423 nsNativeThemeGTK::DrawWidgetBackground(nsIRenderingContext* aContext,
00424                                        nsIFrame* aFrame,
00425                                        PRUint8 aWidgetType,
00426                                        const nsRect& aRect,
00427                                        const nsRect& aClipRect)
00428 {
00429   GtkWidgetState state;
00430   GtkThemeWidgetType gtkWidgetType;
00431   gint flags;
00432   if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, &state,
00433                             &flags))
00434     return NS_OK;
00435 
00436   nsDrawingSurfaceGTK* surface;
00437   aContext->GetDrawingSurface((nsIDrawingSurface**)&surface);
00438   NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
00439   GdkWindow* window = (GdkWindow*) surface->GetDrawable();
00440   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
00441 
00442   nsTransform2D* transformMatrix;
00443   aContext->GetCurrentTransform(transformMatrix);
00444 
00445   nsRect tr(aRect);
00446   transformMatrix->TransformCoord(&tr.x, &tr.y, &tr.width, &tr.height);
00447   GdkRectangle gdk_rect = {tr.x, tr.y, tr.width, tr.height};
00448 
00449   nsRect cr(aClipRect);
00450   transformMatrix->TransformCoord(&cr.x, &cr.y, &cr.width, &cr.height);
00451   GdkRectangle gdk_clip = {cr.x, cr.y, cr.width, cr.height};
00452 
00453   NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType),
00454                "Trying to render an unsafe widget!");
00455 
00456   PRBool safeState = IsWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
00457   XErrorHandler oldHandler = nsnull;
00458   if (!safeState) {
00459     gLastXError = 0;
00460     oldHandler = XSetErrorHandler(NativeThemeErrorHandler);
00461   }
00462 
00463   moz_gtk_widget_paint(gtkWidgetType, window, &gdk_rect, &gdk_clip, &state,
00464                        flags);
00465 
00466   if (!safeState) {
00467     gdk_flush();
00468     XSetErrorHandler(oldHandler);
00469 
00470     if (gLastXError) {
00471 #ifdef DEBUG
00472       printf("GTK theme failed for widget type %d, error was %d, state was "
00473              "[active=%d,focused=%d,inHover=%d,disabled=%d]\n",
00474              aWidgetType, gLastXError, state.active, state.focused,
00475              state.inHover, state.disabled);
00476 #endif
00477       NS_WARNING("GTK theme failed; disabling unsafe widget");
00478       SetWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType);
00479       // force refresh of the window, because the widget was not
00480       // successfully drawn it must be redrawn using the default look
00481       RefreshWidgetWindow(aFrame);
00482     } else {
00483       SetWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
00484     }
00485   }
00486 
00487   return NS_OK;
00488 }
00489 
00490 NS_IMETHODIMP
00491 nsNativeThemeGTK::GetWidgetBorder(nsIDeviceContext* aContext, nsIFrame* aFrame,
00492                                   PRUint8 aWidgetType, nsMargin* aResult)
00493 {
00494   aResult->top = aResult->left = 0;
00495   switch (aWidgetType) {
00496   case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
00497   case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
00498     {
00499       MozGtkScrollbarMetrics metrics;
00500       moz_gtk_get_scrollbar_metrics(&metrics);
00501       aResult->top = aResult->left = metrics.trough_border;
00502     }
00503     break;
00504   case NS_THEME_TOOLBOX:
00505     // gtk has no toolbox equivalent.  So, although we map toolbox to
00506     // gtk's 'toolbar' for purposes of painting the widget background,
00507     // we don't use the toolbar border for toolbox.
00508     break;
00509   case NS_THEME_TOOLBAR_DUAL_BUTTON:
00510     // TOOLBAR_DUAL_BUTTON is an interesting case.  We want a border to draw
00511     // around the entire button + dropdown, and also an inner border if you're
00512     // over the button part.  But, we want the inner button to be right up
00513     // against the edge of the outer button so that the borders overlap.
00514     // To make this happen, we draw a button border for the outer button,
00515     // but don't reserve any space for it.
00516     break;
00517   default:
00518     {
00519       GtkThemeWidgetType gtkWidgetType;
00520       if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nsnull,
00521                                nsnull))
00522         moz_gtk_get_widget_border(gtkWidgetType, &aResult->left,
00523                                   &aResult->top);
00524     }
00525   }
00526 
00527   aResult->right = aResult->left;
00528   aResult->bottom = aResult->top;
00529 
00530   return NS_OK;
00531 }
00532 
00533 PRBool
00534 nsNativeThemeGTK::GetWidgetPadding(nsIDeviceContext* aContext,
00535                                    nsIFrame* aFrame, PRUint8 aWidgetType,
00536                                    nsMargin* aResult)
00537 {
00538   if (aWidgetType == NS_THEME_BUTTON_FOCUS ||
00539       aWidgetType == NS_THEME_TOOLBAR_BUTTON ||
00540       aWidgetType == NS_THEME_TOOLBAR_DUAL_BUTTON) {
00541     aResult->SizeTo(0, 0, 0, 0);
00542     return PR_TRUE;
00543   }
00544 
00545   return PR_FALSE;
00546 }
00547 
00548 PRBool
00549 nsNativeThemeGTK::GetWidgetOverflow(nsIDeviceContext* aContext,
00550                                     nsIFrame* aFrame, PRUint8 aWidgetType,
00551                                     nsRect* aResult)
00552 {
00553   nsIntMargin extraSize(0,0,0,0);
00554   // Allow an extra one pixel above and below the thumb for certain
00555   // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least);
00556   // see moz_gtk_scrollbar_thumb_paint in gtk2drawing.c
00557   switch (aWidgetType) {
00558   case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
00559     extraSize.top = extraSize.bottom = 1;
00560     break;
00561   case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
00562     extraSize.left = extraSize.right = 1;
00563     break;
00564   default:
00565     return PR_FALSE;
00566   }
00567 
00568   float p2t = aContext->DevUnitsToAppUnits();
00569   nsMargin m(NSIntPixelsToTwips(extraSize.left, p2t),
00570              NSIntPixelsToTwips(extraSize.top, p2t),
00571              NSIntPixelsToTwips(extraSize.right, p2t),
00572              NSIntPixelsToTwips(extraSize.bottom, p2t));
00573   nsRect r(nsPoint(0, 0), aFrame->GetSize());
00574   r.Inflate(m);
00575   *aResult = r;
00576   return PR_TRUE;
00577 }
00578 
00579 NS_IMETHODIMP
00580 nsNativeThemeGTK::GetMinimumWidgetSize(nsIRenderingContext* aContext,
00581                                        nsIFrame* aFrame, PRUint8 aWidgetType,
00582                                        nsSize* aResult, PRBool* aIsOverridable)
00583 {
00584   aResult->width = aResult->height = 0;
00585   *aIsOverridable = PR_TRUE;
00586 
00587   switch (aWidgetType) {
00588     case NS_THEME_SCROLLBAR_BUTTON_UP:
00589     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
00590       {
00591         MozGtkScrollbarMetrics metrics;
00592         moz_gtk_get_scrollbar_metrics(&metrics);
00593 
00594         aResult->width = metrics.slider_width;
00595         aResult->height = metrics.stepper_size;
00596         *aIsOverridable = PR_FALSE;
00597       }
00598       break;
00599     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
00600     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
00601       {
00602         MozGtkScrollbarMetrics metrics;
00603         moz_gtk_get_scrollbar_metrics(&metrics);
00604 
00605         aResult->width = metrics.stepper_size;
00606         aResult->height = metrics.slider_width;
00607         *aIsOverridable = PR_FALSE;
00608       }
00609       break;
00610     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
00611     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
00612       {
00613         MozGtkScrollbarMetrics metrics;
00614         moz_gtk_get_scrollbar_metrics(&metrics);
00615 
00616         if (aWidgetType == NS_THEME_SCROLLBAR_THUMB_VERTICAL) {
00617           aResult->width = metrics.slider_width;
00618           aResult->height = metrics.min_slider_size;
00619         } else {
00620           aResult->width = metrics.min_slider_size;
00621           aResult->height = metrics.slider_width;
00622         }
00623 
00624         *aIsOverridable = PR_FALSE;
00625       }
00626       break;
00627   case NS_THEME_DROPDOWN_BUTTON:
00628     {
00629       moz_gtk_get_dropdown_arrow_size(&aResult->width, &aResult->height);
00630       *aIsOverridable = PR_FALSE;
00631     }
00632     break;
00633   case NS_THEME_CHECKBOX:
00634   case NS_THEME_RADIO:
00635     {
00636       gint indicator_size, indicator_spacing;
00637 
00638       if (aWidgetType == NS_THEME_CHECKBOX) {
00639         moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing);
00640       } else {
00641         moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
00642       }
00643 
00644       // Include space for the indicator and the padding around it.
00645       aResult->width = indicator_size + 3 * indicator_spacing;
00646       aResult->height = indicator_size + 2 * indicator_spacing;
00647       *aIsOverridable = PR_FALSE;
00648     }
00649     break;
00650 
00651   case NS_THEME_CHECKBOX_CONTAINER:
00652   case NS_THEME_RADIO_CONTAINER:
00653   case NS_THEME_CHECKBOX_LABEL:
00654   case NS_THEME_RADIO_LABEL:
00655   case NS_THEME_BUTTON:
00656   case NS_THEME_TOOLBAR_BUTTON:
00657     {
00658       // Just include our border, and let the box code augment the size.
00659 
00660       nsCOMPtr<nsIDeviceContext> dc;
00661       aContext->GetDeviceContext(*getter_AddRefs(dc));
00662 
00663       nsMargin border;
00664       nsNativeThemeGTK::GetWidgetBorder(dc, aFrame, aWidgetType, &border);
00665       aResult->width = border.left + border.right;
00666       aResult->height = border.top + border.bottom;
00667     }
00668     break;
00669   }
00670 
00671   return NS_OK;
00672 }
00673 
00674 NS_IMETHODIMP
00675 nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame, PRUint8 aWidgetType, 
00676                                      nsIAtom* aAttribute, PRBool* aShouldRepaint)
00677 {
00678   // Some widget types just never change state.
00679   if (aWidgetType == NS_THEME_TOOLBOX ||
00680       aWidgetType == NS_THEME_TOOLBAR ||
00681       aWidgetType == NS_THEME_STATUSBAR ||
00682       aWidgetType == NS_THEME_STATUSBAR_PANEL ||
00683       aWidgetType == NS_THEME_STATUSBAR_RESIZER_PANEL ||
00684       aWidgetType == NS_THEME_PROGRESSBAR_CHUNK ||
00685       aWidgetType == NS_THEME_PROGRESSBAR_CHUNK_VERTICAL ||
00686       aWidgetType == NS_THEME_PROGRESSBAR ||
00687       aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL ||
00688       aWidgetType == NS_THEME_MENUBAR ||
00689       aWidgetType == NS_THEME_MENUPOPUP ||
00690       aWidgetType == NS_THEME_TOOLTIP ||
00691       aWidgetType == NS_THEME_WINDOW ||
00692       aWidgetType == NS_THEME_DIALOG) {
00693     *aShouldRepaint = PR_FALSE;
00694     return NS_OK;
00695   }
00696 
00697   // XXXdwh Not sure what can really be done here.  Can at least guess for
00698   // specific widgets that they're highly unlikely to have certain states.
00699   // For example, a toolbar doesn't care about any states.
00700   if (!aAttribute) {
00701     // Hover/focus/active changed.  Always repaint.
00702     *aShouldRepaint = PR_TRUE;
00703   }
00704   else {
00705     // Check the attribute to see if it's relevant.  
00706     // disabled, checked, dlgtype, default, etc.
00707     *aShouldRepaint = PR_FALSE;
00708     if (aAttribute == mDisabledAtom || aAttribute == mCheckedAtom ||
00709         aAttribute == mSelectedAtom || aAttribute == mFocusedAtom ||
00710         aAttribute == mMenuActiveAtom)
00711       *aShouldRepaint = PR_TRUE;
00712   }
00713 
00714   return NS_OK;
00715 }
00716 
00717 NS_IMETHODIMP
00718 nsNativeThemeGTK::ThemeChanged()
00719 {
00720   nsDeviceContextGTK::ClearCachedSystemFonts();
00721 
00722   memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
00723   return NS_OK;
00724 }
00725 
00726 NS_IMETHODIMP_(PRBool)
00727 nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
00728                                       nsIFrame* aFrame,
00729                                       PRUint8 aWidgetType)
00730 {
00731   if (aFrame) {
00732     // For now don't support HTML.
00733     if (aFrame->GetContent()->IsContentOfType(nsIContent::eHTML))
00734       return PR_FALSE;
00735   }
00736 
00737   if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType))
00738     return PR_FALSE;
00739 
00740   switch (aWidgetType) {
00741   case NS_THEME_BUTTON:
00742   case NS_THEME_BUTTON_FOCUS:
00743   case NS_THEME_RADIO:
00744   case NS_THEME_CHECKBOX:
00745   case NS_THEME_TOOLBOX: // N/A
00746   case NS_THEME_TOOLBAR:
00747   case NS_THEME_TOOLBAR_BUTTON:
00748   case NS_THEME_TOOLBAR_DUAL_BUTTON: // so we can override the border with 0
00749     // case NS_THEME_TOOLBAR_DUAL_BUTTON_DROPDOWN:
00750     // case NS_THEME_TOOLBAR_SEPARATOR:
00751   case NS_THEME_TOOLBAR_GRIPPER:
00752   case NS_THEME_STATUSBAR:
00753   case NS_THEME_STATUSBAR_PANEL:
00754     // case NS_THEME_RESIZER:  (n/a for gtk)
00755     // case NS_THEME_LISTBOX:
00756     // case NS_THEME_LISTBOX_LISTITEM:
00757     // case NS_THEME_TREEVIEW:
00758     // case NS_THEME_TREEVIEW_TREEITEM:
00759     // case NS_THEME_TREEVIEW_TWISTY:
00760     // case NS_THEME_TREEVIEW_LINE:
00761     // case NS_THEME_TREEVIEW_HEADER:
00762     // case NS_THEME_TREEVIEW_HEADER_CELL:
00763     // case NS_THEME_TREEVIEW_HEADER_SORTARROW:
00764     // case NS_THEME_TREEVIEW_TWISTY_OPEN:
00765     case NS_THEME_PROGRESSBAR:
00766     case NS_THEME_PROGRESSBAR_CHUNK:
00767     case NS_THEME_PROGRESSBAR_VERTICAL:
00768     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
00769     case NS_THEME_TAB:
00770     // case NS_THEME_TAB_PANEL:
00771     case NS_THEME_TAB_LEFT_EDGE:
00772     case NS_THEME_TAB_RIGHT_EDGE:
00773     case NS_THEME_TAB_PANELS:
00774   case NS_THEME_TOOLTIP:
00775     // case NS_THEME_SPINNER:
00776     // case NS_THEME_SPINNER_UP_BUTTON:
00777     // case NS_THEME_SPINNER_DOWN_BUTTON:
00778     // case NS_THEME_SCROLLBAR:  (n/a for gtk)
00779   case NS_THEME_SCROLLBAR_BUTTON_UP:
00780   case NS_THEME_SCROLLBAR_BUTTON_DOWN:
00781   case NS_THEME_SCROLLBAR_BUTTON_LEFT:
00782   case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
00783   case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
00784   case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
00785   case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
00786   case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
00787     // case NS_THEME_SCROLLBAR_GRIPPER_HORIZONTAL:  (n/a for gtk)
00788     // case NS_THEME_SCROLLBAR_GRIPPER_VERTICAL:  (n/a for gtk)
00789   case NS_THEME_TEXTFIELD:
00790     // case NS_THEME_TEXTFIELD_CARET:
00791   case NS_THEME_DROPDOWN_BUTTON:
00792   case NS_THEME_DROPDOWN_TEXTFIELD:
00793     // case NS_THEME_SLIDER:
00794     // case NS_THEME_SLIDER_THUMB:
00795     // case NS_THEME_SLIDER_THUMB_START:
00796     // case NS_THEME_SLIDER_THUMB_END:
00797     // case NS_THEME_SLIDER_TICK:
00798   case NS_THEME_CHECKBOX_CONTAINER:
00799   case NS_THEME_RADIO_CONTAINER:
00800   case NS_THEME_CHECKBOX_LABEL:
00801   case NS_THEME_RADIO_LABEL:
00802 #ifdef MOZ_WIDGET_GTK2
00803   case NS_THEME_MENUBAR:
00804   case NS_THEME_MENUPOPUP:
00805   case NS_THEME_MENUITEM:
00806   case NS_THEME_CHECKMENUITEM:
00807   case NS_THEME_RADIOMENUITEM:
00808   case NS_THEME_WINDOW:
00809   case NS_THEME_DIALOG:
00810   case NS_THEME_DROPDOWN:
00811   case NS_THEME_DROPDOWN_TEXT:
00812 #endif
00813     return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
00814   }
00815 
00816   return PR_FALSE;
00817 }
00818 
00819 NS_IMETHODIMP_(PRBool)
00820 nsNativeThemeGTK::WidgetIsContainer(PRUint8 aWidgetType)
00821 {
00822   // XXXdwh At some point flesh all of this out.
00823   if (aWidgetType == NS_THEME_DROPDOWN_BUTTON || 
00824       aWidgetType == NS_THEME_RADIO ||
00825       aWidgetType == NS_THEME_CHECKBOX)
00826     return PR_FALSE;
00827   return PR_TRUE;
00828 }