diff --git a/widget/src/gtk2/gtk2drawing.c b/widget/src/gtk2/gtk2drawing.c index ada52ab8a7ce..ae51129626d0 100644 --- a/widget/src/gtk2/gtk2drawing.c +++ b/widget/src/gtk2/gtk2drawing.c @@ -1677,74 +1677,150 @@ moz_gtk_progress_chunk_paint(GdkDrawable* drawable, GdkRectangle* rect, return MOZ_GTK_SUCCESS; } +gint +moz_gtk_get_tab_thickness(void) +{ + ensure_tab_widget(); + if (YTHICKNESS(gTabWidget->style) < 2) + return 2; /* some themes don't set ythickness correctly */ + + return YTHICKNESS(gTabWidget->style); +} + static gint moz_gtk_tab_paint(GdkDrawable* drawable, GdkRectangle* rect, - GdkRectangle* cliprect, gint flags, + GdkRectangle* cliprect, GtkTabFlags flags, GtkTextDirection direction) { - /* - * In order to get the correct shadows and highlights, GTK paints - * tabs right-to-left (end-to-beginning, to be generic), leaving - * out the active tab, and then paints the current tab once - * everything else is painted. In addition, GTK uses a 2-pixel - * overlap between adjacent tabs (this value is hard-coded in - * gtknotebook.c). For purposes of mapping to gecko's frame - * positions, we put this overlap on the far edge of the frame - * (i.e., for a horizontal/top tab strip, we shift the left side - * of each tab 2px to the left, into the neighboring tab's frame - * rect. The right 2px * of a tab's frame will be referred to as - * the "overlap area". - * - * Since we can't guarantee painting order with gecko, we need to - * manage the overlap area manually. There are three types of tab - * boundaries we need to handle: - * - * * two non-active tabs: In this case, we just have both tabs - * paint normally. - * - * * non-active to active tab: Here, we need the tab on the left to paint - * itself normally, then paint the edge of the - * active tab in its overlap area. - * - * * active to non-active tab: In this case, we just have both tabs paint - * normally. - * - * We need to make an exception for the first tab - since there is - * no tab to the left to paint the overlap area, we do _not_ shift - * the tab left by 2px. - */ + /* When the tab isn't selected, we just draw a notebook extension. + * When it is selected, we overwrite the adjacent border of the tabpanel + * touching the tab with a pierced border (called "the gap") to make the + * tab appear physically attached to the tabpanel; see details below. */ GtkStyle* style; + ensure_tab_widget(); gtk_widget_set_direction(gTabWidget, direction); - if (!(flags & MOZ_GTK_TAB_FIRST)) { - rect->x -= 2; - rect->width += 2; - } - style = gTabWidget->style; TSOffsetStyleGCs(style, rect->x, rect->y); - gtk_paint_extension(style, drawable, - ((flags & MOZ_GTK_TAB_SELECTED) ? - GTK_STATE_NORMAL : GTK_STATE_ACTIVE), - GTK_SHADOW_OUT, cliprect, gTabWidget, "tab", rect->x, - rect->y, rect->width, rect->height, GTK_POS_BOTTOM); - if (flags & MOZ_GTK_TAB_BEFORE_SELECTED) { - gboolean before_selected = ((flags & MOZ_GTK_TAB_BEFORE_SELECTED)!=0); - cliprect->y -= 2; - cliprect->height += 2; - rect->y -= 2 * before_selected; - rect->x += rect->width - 2; - - TSOffsetStyleGCs(style, rect->x, rect->y); - - gtk_paint_extension(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + if ((flags & MOZ_GTK_TAB_SELECTED) == 0) { + /* Only draw the tab */ + gtk_paint_extension(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_OUT, cliprect, gTabWidget, "tab", - rect->x, rect->y, rect->width, - rect->height + (2 * before_selected), - GTK_POS_BOTTOM); + rect->x, rect->y, rect->width, rect->height, + (flags & MOZ_GTK_TAB_BOTTOM) ? + GTK_POS_TOP : GTK_POS_BOTTOM ); + } else { + /* Draw the tab and the gap + * We want the gap to be positionned exactly on the tabpanel top + * border; since tabbox.css may set a negative margin so that the tab + * frame rect already overlaps the tabpanel frame rect, we need to take + * that into account when drawing. To that effect, nsNativeThemeGTK + * passes us this negative margin (bmargin in the graphic below) in the + * lowest bits of |flags|. We use it to set gap_voffset, the distance + * between the top of the gap and the bottom of the tab (resp. the + * bottom of the gap and the top of the tab when we draw a bottom tab), + * while ensuring that the gap always touches the border of the tab, + * i.e. 0 <= gap_voffset <= gap_height, to avoid surprinsing results + * with big negative or positive margins. + * Here is a graphical explanation in the case of top tabs: + * ___________________________ + * / \ + * | T A B | + * ----------|. . . . . . . . . . . . . . .|----- top of tabpanel + * : ^ bmargin : ^ + * : | (-negative margin, : | + * bottom : v passed in flags) : | gap_height + * of -> :.............................: | (the size of the + * the tab . part of the gap . | tabpanel top border) + * . outside of the tab . v + * ---------------------------------------------- + * + * To draw the gap, we use gtk_paint_box_gap(), see comment in + * moz_gtk_tabpanels_paint(). This box_gap is made 3 * gap_height tall, + * which should suffice to ensure that the only visible border is the + * pierced one. If the tab is in the middle, we make the box_gap begin + * a bit to the left of the tab and end a bit to the right, adjusting + * the gap position so it still is under the tab, because we want the + * rendering of a gap in the middle of a tabpanel. This is the role of + * the gints gap_{l,r}_offset. On the contrary, if the tab is the + * first, we align the start border of the box_gap with the start + * border of the tab (left if LTR, right if RTL), by setting the + * appropriate offset to 0.*/ + gint gap_loffset, gap_roffset, gap_voffset, gap_height; + + /* Get height needed by the gap */ + gap_height = moz_gtk_get_tab_thickness(); + + /* Extract gap_voffset from the first bits of flags */ + gap_voffset = flags & MOZ_GTK_TAB_MARGIN_MASK; + if (gap_voffset > gap_height) + gap_voffset = gap_height; + + /* Set gap_{l,r}_offset to appropriate values */ + gap_loffset = gap_roffset = 20; /* should be enough */ + if (flags & MOZ_GTK_TAB_FIRST) { + if (direction == GTK_TEXT_DIR_RTL) + gap_roffset = 0; + else + gap_loffset = 0; + } + + if (flags & MOZ_GTK_TAB_BOTTOM) { + /* Enlarge the cliprect to have room for the full gap height */ + cliprect->height += gap_height - gap_voffset; + cliprect->y -= gap_height - gap_voffset; + + /* Draw the tab */ + gtk_paint_extension(style, drawable, GTK_STATE_NORMAL, + GTK_SHADOW_OUT, cliprect, gTabWidget, "tab", + rect->x, rect->y + gap_voffset, rect->width, + rect->height - gap_voffset, GTK_POS_TOP); + + /* Draw the gap; erase with background color before painting in + * case theme does not */ + gtk_style_apply_default_background(style, drawable, TRUE, + GTK_STATE_NORMAL, cliprect, + rect->x, + rect->y + gap_voffset + - gap_height, + rect->width, gap_height); + gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + cliprect, gTabWidget, "notebook", + rect->x - gap_loffset, + rect->y + gap_voffset - 3 * gap_height, + rect->width + gap_loffset + gap_roffset, + 3 * gap_height, GTK_POS_BOTTOM, + gap_loffset, rect->width); + } else { + /* Enlarge the cliprect to have room for the full gap height */ + cliprect->height += gap_height - gap_voffset; + + /* Draw the tab */ + gtk_paint_extension(style, drawable, GTK_STATE_NORMAL, + GTK_SHADOW_OUT, cliprect, gTabWidget, "tab", + rect->x, rect->y, rect->width, + rect->height - gap_voffset, GTK_POS_BOTTOM); + + /* Draw the gap; erase with background color before painting in + * case theme does not */ + gtk_style_apply_default_background(style, drawable, TRUE, + GTK_STATE_NORMAL, cliprect, + rect->x, + rect->y + rect->height + - gap_voffset, + rect->width, gap_height); + gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + cliprect, gTabWidget, "notebook", + rect->x - gap_loffset, + rect->y + rect->height - gap_voffset, + rect->width + gap_loffset + gap_roffset, + 3 * gap_height, GTK_POS_TOP, + gap_loffset, rect->width); + } + } return MOZ_GTK_SUCCESS; @@ -1754,6 +1830,10 @@ static gint moz_gtk_tabpanels_paint(GdkDrawable* drawable, GdkRectangle* rect, GdkRectangle* cliprect, GtkTextDirection direction) { + /* We use gtk_paint_box_gap() to draw the tabpanels widget. gtk_paint_box() + * draws an all-purpose box, which a lot of themes render differently. + * A zero-width gap is still visible in most themes, so we hide it to the + * left (10px should be enough) */ GtkStyle* style; ensure_tab_widget(); @@ -1762,9 +1842,10 @@ moz_gtk_tabpanels_paint(GdkDrawable* drawable, GdkRectangle* rect, style = gTabWidget->style; TSOffsetStyleGCs(style, rect->x, rect->y); - gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, - cliprect, gTabWidget, "notebook", rect->x, rect->y, - rect->width, rect->height); + gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + cliprect, gTabWidget, "notebook", rect->x, rect->y, + rect->width, rect->height, + GTK_POS_TOP, -10, 0); return MOZ_GTK_SUCCESS; } @@ -2198,6 +2279,10 @@ moz_gtk_get_widget_border(GtkThemeWidgetType widget, gint* left, gint* top, ensure_check_menu_item_widget(); w = gCheckMenuItemWidget; break; + case MOZ_GTK_TAB: + ensure_tab_widget(); + w = gTabWidget; + break; /* These widgets have no borders, since they are not containers. */ case MOZ_GTK_SPLITTER_HORIZONTAL: case MOZ_GTK_SPLITTER_VERTICAL: @@ -2481,7 +2566,8 @@ moz_gtk_widget_paint(GtkThemeWidgetType widget, GdkDrawable* drawable, direction); break; case MOZ_GTK_TAB: - return moz_gtk_tab_paint(drawable, rect, cliprect, flags, direction); + return moz_gtk_tab_paint(drawable, rect, cliprect, + (GtkTabFlags) flags, direction); break; case MOZ_GTK_TABPANELS: return moz_gtk_tabpanels_paint(drawable, rect, cliprect, direction); diff --git a/widget/src/gtk2/gtkdrawing.h b/widget/src/gtk2/gtkdrawing.h index bc2d183e109b..baf1d27f0450 100644 --- a/widget/src/gtk2/gtkdrawing.h +++ b/widget/src/gtk2/gtkdrawing.h @@ -80,12 +80,14 @@ typedef struct { /** flags for tab state **/ typedef enum { + /* first eight bits are used to pass a margin */ + MOZ_GTK_TAB_MARGIN_MASK = 0xFF, + /* bottom tabs */ + MOZ_GTK_TAB_BOTTOM = 1 << 8, /* the first tab in the group */ - MOZ_GTK_TAB_FIRST = 1 << 0, - /* the tab just before the selected tab */ - MOZ_GTK_TAB_BEFORE_SELECTED = 1 << 1, + MOZ_GTK_TAB_FIRST = 1 << 9, /* the selected tab */ - MOZ_GTK_TAB_SELECTED = 1 << 2 + MOZ_GTK_TAB_SELECTED = 1 << 10 } GtkTabFlags; /** flags for menuitems **/ @@ -356,6 +358,11 @@ gint moz_gtk_splitter_get_metrics(gint orientation, gint* size); */ GtkWidget* moz_gtk_get_scrollbar_widget(void); +/** + * Get the YTHICKNESS of a tab (notebook extension). + */ +gint moz_gtk_get_tab_thickness(void); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/widget/src/gtk2/nsNativeThemeGTK.cpp b/widget/src/gtk2/nsNativeThemeGTK.cpp index d811d536fbe9..0867e9adcd56 100644 --- a/widget/src/gtk2/nsNativeThemeGTK.cpp +++ b/widget/src/gtk2/nsNativeThemeGTK.cpp @@ -499,20 +499,28 @@ nsNativeThemeGTK::GetGtkWidgetAndState(PRUint8 aWidgetType, nsIFrame* aFrame, aGtkWidgetType = MOZ_GTK_TABPANELS; break; case NS_THEME_TAB: - case NS_THEME_TAB_LEFT_EDGE: - case NS_THEME_TAB_RIGHT_EDGE: { if (aWidgetFlags) { - *aWidgetFlags = 0; + /* 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). */ + gint margin; + if (IsBottomTab(aFrame)) { + *aWidgetFlags = MOZ_GTK_TAB_BOTTOM; + margin = aFrame->GetUsedMargin().top; + } else { + *aWidgetFlags = 0; + margin = aFrame->GetUsedMargin().bottom; + } - if (aWidgetType == NS_THEME_TAB && - CheckBooleanAttr(aFrame, nsWidgetAtoms::selected)) + *aWidgetFlags |= PR_MIN(MOZ_GTK_TAB_MARGIN_MASK, + PR_MAX(0, aFrame->PresContext()-> + AppUnitsToDevPixels(-margin) )); + + if (IsSelectedTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_SELECTED; - else if (aWidgetType == NS_THEME_TAB_LEFT_EDGE) - *aWidgetFlags |= MOZ_GTK_TAB_BEFORE_SELECTED; - if (aFrame->GetContent()->HasAttr(kNameSpaceID_None, - nsWidgetAtoms::firsttab)) + if (IsFirstTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_FIRST; } @@ -797,6 +805,7 @@ NS_IMETHODIMP nsNativeThemeGTK::GetWidgetBorder(nsIDeviceContext* aContext, nsIFrame* aFrame, PRUint8 aWidgetType, nsMargin* aResult) { + GtkTextDirection direction = GetTextDirection(aFrame); aResult->top = aResult->left = aResult->right = aResult->bottom = 0; switch (aWidgetType) { case NS_THEME_SCROLLBAR_TRACK_VERTICAL: @@ -820,15 +829,25 @@ nsNativeThemeGTK::GetWidgetBorder(nsIDeviceContext* aContext, nsIFrame* aFrame, // 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: + // Top tabs have no bottom border, bottom tabs have no top border + moz_gtk_get_widget_border(MOZ_GTK_TAB, &aResult->left, &aResult->top, + &aResult->right, &aResult->bottom, direction, + FALSE); + if (IsBottomTab(aFrame)) + aResult->top = 0; + else + aResult->bottom = 0; + break; default: { GtkThemeWidgetType gtkWidgetType; - GtkTextDirection direction = GetTextDirection(aFrame); if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nsnull, nsnull)) moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top, &aResult->right, &aResult->bottom, direction, - (aFrame ? aFrame->GetContent()->IsNodeOfType(nsINode::eHTML) : FALSE)); + aFrame && aFrame->GetContent()-> + IsNodeOfType(nsINode::eHTML)); } } return NS_OK; @@ -855,8 +874,24 @@ nsNativeThemeGTK::GetWidgetOverflow(nsIDeviceContext* aContext, nsRect* aResult) { nsIntMargin extraSize; - if (!GetExtraSizeForWidget(aWidgetType, &extraSize)) + if (aWidgetType == NS_THEME_TAB) + { + if (!IsSelectedTab(aFrame)) + return PR_FALSE; + + if (IsBottomTab(aFrame)) { + extraSize = nsMargin(0, aFrame->PresContext()-> + DevPixelsToAppUnits(moz_gtk_get_tab_thickness()) + + PR_MIN(0, aFrame->GetUsedMargin().top), 0, 0); + } else { + extraSize = nsMargin(0, 0, 0, aFrame->PresContext()-> + DevPixelsToAppUnits(moz_gtk_get_tab_thickness()) + + PR_MIN(0, aFrame->GetUsedMargin().bottom)); + } + } + else if (!GetExtraSizeForWidget(aWidgetType, &extraSize)) return PR_FALSE; + PRInt32 p2a = aContext->AppUnitsPerDevPixel(); nsMargin m(NSIntPixelsToAppUnits(extraSize.left, p2a), NSIntPixelsToAppUnits(extraSize.top, p2a), @@ -1147,8 +1182,6 @@ nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext, case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL: case NS_THEME_TAB: // case NS_THEME_TAB_PANEL: - case NS_THEME_TAB_LEFT_EDGE: - case NS_THEME_TAB_RIGHT_EDGE: case NS_THEME_TAB_PANELS: case NS_THEME_TOOLTIP: case NS_THEME_SPINNER: