/***** * paint.c : XmHTML rendering routines * * This file Version $Revision: 1.11 $ * * Creation date: Fri Dec 6 19:50:20 GMT+0100 1996 * Last modification: $Date: 1999/07/29 01:26:29 $ * By: $Author: sopwith $ * Current State: $State: Exp $ * * Author: newt * (C)Copyright 1995-1996 Ripley Software Development * All Rights Reserved * * This file is part of the XmHTML Widget Library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *****/ /***** * ChangeLog * $Log: paint.c,v $ * Revision 1.11 1999/07/29 01:26:29 sopwith * * * Fix all warnings. * * Revision 1.10 1999/06/02 01:00:43 unammx * * 1999-06-01 Akira Higuchi * * * libgnomeui/gnome-canvas-text.c: * * libgnomeui/gnome-icon-item.c: * * libgnomeui/gnome-less.c: Replace some gdk_font_load() calls with * gdk_fontset_load. Use a more open fontset rule to load the fonts. * * 1999-06-01 Akira Higuchi * * * gtk-xmhtml/XmHTMLP.h: Add three members lbearing, rbearing, * and width. These members are computed in allocFont(). * * * gtk-xmhtml/toolkit.h: Remove Toolkit_XFont() macro. * * * gtk-xmhtml/XmHTML.c: * * gtk-xmhtml/fonts.c: * * gtk-xmhtml/format.c: * * gtk-xmhtml/gtk-xmhtml.c: * * gtk-xmhtml/layout.c: * * gtk-xmhtml/paint.c: Add fontset support. We use gdk_fontset_load() * instead of gdk_font_load() iff a fontset is supplied for label * widgets. * * * gtk-xmhtml/test.c: Add gtk_set_locale() call before gtk_init(). * * Revision 1.9 1998/10/09 21:34:30 mila * ChangeLog * * Revision 1.8 1998/02/12 03:09:38 unammx * Merge to Koen's XmHTML 1.1.2 + following fixes: * * Wed Feb 11 20:27:19 1998 Miguel de Icaza * * * gtk-forms.c (freeForm): gtk_destroy_widget is no longer needed * with the refcounting changes. * * * gtk-xmhtml.c (gtk_xmhtml_remove): Only god knows why I was * adding the just removed widget. * * Revision 1.7 1998/01/06 02:56:35 unammx * Fixed bogus call to gtk_timeout_add() that caused animated images to be * repainted incorrectly - Federico * * Revision 1.6 1997/12/29 22:16:32 unammx * This version does: * * - Sync with Koen to version Beta 1.1.2c of the XmHTML widget. * Includes various table fixes. * * - Callbacks are now properly checked for the Gtk edition (ie, * signals). * * Revision 1.5 1997/12/26 21:03:34 sopwith * A few miscellaneous XmHTML bug fixes, including a note to miguel so he can fix frames ;-) * * Revision 1.4 1997/12/25 01:38:29 unammx * Small bug fixes * * Revision 1.3 1997/12/25 01:34:13 unammx * Good news for the day: * * I have upgraded our XmHTML sources to XmHTML 1.1.1. * * This basically means that we got table support :-) * * Still left to do: * * - Set/Get gtk interface for all of the toys in the widget. * - Frame support is broken, dunno why. * - Form support (ie adding widgets to it) * * Miguel. * * Revision 1.2 1997/12/11 21:20:23 unammx * Step 2: more gtk/xmhtml code, still non-working - mig * * Revision 1.1 1997/11/28 03:38:58 gnomecvs * Work in progress port of XmHTML; No, it does not compile, don't even try -mig * * Revision 1.17 1997/10/23 00:25:06 newt * XmHTML Beta 1.1.0 release * * Revision 1.16 1997/08/31 17:37:20 newt * log edit * * Revision 1.15 1997/08/30 01:17:29 newt * Text layout for
 is now properly handled.
* Added anchor highlighting support.
* Generalized anchor button rendering.
* Added support for colored 
. * * Revision 1.14 1997/08/01 13:05:28 newt * Lots of bugfixes: baseline adjustment of images, forms components or large * fonts, animated gif disposal handling. Reduced data storage. * * Revision 1.13 1997/05/28 01:52:43 newt * Bugfix 05/26/97-01. Form layout changes. * * Revision 1.12 1997/04/29 14:30:19 newt * Bugfix 04/26/97-01. Fixed SetText to use dimensions of baseline word when * updating the object data. * * Revision 1.11 1997/04/03 05:40:21 newt * Anchor and word rendering of mixed fonts finally works. Fixed anchor * transparency for documents with a body images. Fixed image anchor scrolling * problem. * * Revision 1.10 1997/03/28 07:18:37 newt * Alignment bugfix: now only done with a positive offset. * Bugfix in frame disposal: index was one frame too early. * Bugfix in image anchor rendering: now done before checking exposure region. * * Revision 1.9 1997/03/20 08:12:38 newt * SetText: images now use their vertical alignment specs for baseline * adjustment. * * Revision 1.8 1997/03/11 19:57:04 newt * SetText now does both text and image layout. * DrawImage now does animated gifs and transparent images * * Revision 1.7 1997/03/04 18:48:52 newt * Animation stuff. Fixed a spacing bug in SetText * * Revision 1.6 1997/03/04 01:00:55 newt * ? * * Revision 1.5 1997/03/02 23:20:31 newt * Added setting and rendering of images. image-related changes to SetText. * SetText now also ``glues'' words together properly * * Revision 1.4 1997/02/11 02:09:28 newt * Fair amount of changes: text layout has been completely changed * * Revision 1.3 1997/01/09 06:55:47 newt * expanded copyright marker * * Revision 1.2 1997/01/09 06:46:39 newt * lots of changes: painting now divided in a layout en rendering part. * * Revision 1.1 1996/12/19 02:17:12 newt * Initial Revision * *****/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "XmHTMLP.h" #include "XmHTMLfuncs.h" /*** External Function Prototype Declarations ***/ /*** Public Variable Declarations ***/ /*** Private Datatype Declarations ****/ /*** Private Function Prototype Declarations ****/ /* main rendering routines */ static void DrawText(XmHTMLWidget html, XmHTMLObjectTableElement data); static void DrawAnchor(XmHTMLWidget html, XmHTMLObjectTableElement data); static void DrawImageAnchor(XmHTMLWidget html, XmHTMLObjectTableElement data); static void DrawRule(XmHTMLWidget html, XmHTMLObjectTableElement data); static void DrawBullet(XmHTMLWidget html, XmHTMLObjectTableElement data); static XmHTMLObjectTableElement DrawTable(XmHTMLWidget html, XmHTMLObjectTableElement data, XmHTMLObjectTableElement data_end); /* various helper routines */ static void DrawAnchorButton(XmHTMLWidget html, int x, int y, Dimension width, Dimension height, TGC top, TGC bottom); static void DrawFrame(XmHTMLWidget html, XmHTMLImage *image, int xs, int ys); static void DrawTableBorder(XmHTMLWidget html, XmHTMLTable *table); static void DrawCellFrame(XmHTMLWidget html, TableCell *cell); static void DrawCellContent(XmHTMLWidget html, XmHTMLObjectTableElement start, XmHTMLObjectTableElement end); #ifdef WITH_MOTIF static void TimerCB(XtPointer data, XtIntervalId *id); #else int TimerCB(gpointer data); #endif /*** Private Variable Declarations ***/ #define DRAW_TOP (1<<1) #define DRAW_BOTTOM (1<<2) #define DRAW_LEFT (1<<3) #define DRAW_RIGHT (1<<4) #define DRAW_BOX (DRAW_TOP|DRAW_BOTTOM|DRAW_LEFT|DRAW_RIGHT) #define DRAW_NONE ~(DRAW_BOX) /***** * Name: _XmHTMLPaint * Return Type: void * Description: re-paints the given amount of data. * In: * w: widget to paint text to * start: start item * end: end item * Returns: * nothing. *****/ void _XmHTMLPaint(XmHTMLWidget html, XmHTMLObjectTable *start, XmHTMLObjectTable *end) { XmHTMLObjectTableElement temp; _XmHTMLDebug(16, ("paint.c: _XmHTMLPaint Start, paint_start: x = %i, " "y = %i, paint_end: x = %i, y = %i\n", start->x, start->y, end ? end->x : -1, end ? end->y : -1)); temp = start; while(temp && temp != end) { _XmHTMLDebug(16, ("paint.c: _XmHTMLPaint, painting object %s\n", temp->object ? temp->object->element : "")); switch(temp->object_type) { case OBJ_TEXT: case OBJ_PRE_TEXT: /* * First check if this is an image. DrawImage will render * an image as an anchor if required. */ if(temp->text_data & TEXT_IMAGE) _XmHTMLDrawImage(html, temp, 0, False); else { /* form scrolling gets handled by formScroll in XmHTML.c */ if(temp->text_data & TEXT_FORM) break; else { if(temp->text_data & TEXT_ANCHOR) DrawAnchor(html, temp); else DrawText(html, temp); } } break; case OBJ_BULLET: DrawBullet(html, temp); break; case OBJ_HRULE: DrawRule(html, temp); break; case OBJ_IMG: _XmHTMLWarning(__WFUNC__(html, "_XmHTMLPaint"), "Refresh: Invalid image object."); break; case OBJ_TABLE: case OBJ_TABLE_FRAME: temp = DrawTable(html, temp, end); break; case OBJ_APPLET: case OBJ_BLOCK: case OBJ_NONE: break; default: _XmHTMLWarning(__WFUNC__(html, "_XmHTMLPaint"), "Unknown object type!"); } temp = temp->next; } _XmHTMLDebug(16, ("paint.c: _XmHTMLPaint End\n")); return; } /***** * Name: _XmHTMLRestartAnimations * Return Type: void * Description: restarts all animations. Called by SetValues when the * value of the XmNfreezeAnimations resource switches from * True to False. * In: * html: XmHTMLWidget id * Returns: * nothing. *****/ void _XmHTMLRestartAnimations(XmHTMLWidget html) { XmHTMLImage *tmp; for(tmp = html->html.images; tmp != NULL; tmp = tmp->next) { if(ImageIsAnim(tmp)) { tmp->options |= IMG_FRAMEREFRESH; _XmHTMLDrawImage(html, tmp->owner, 0, False); } } } /***** * Same note as for anchor width adjustment applies here to, except that we * _must_ do this locally instead of adjusting the width field of a word * directly. The width field is used by the line breaking algorithm in SetText, * so modifying it will make the words wider every time the widget is resized. * Luckily this is only required for underlined/striked words. *****/ #define AdjustWordWidth { \ width = words[i].width; \ if(i < nwords-1 && words[i].line == words[i+1].line) { \ width = words[i+1].x - words[i].x; \ } \ } /***** * Name: DrawText * Return Type: void * Description: main text rendering engine. * In: * html: XmHTMLWidget id; * data: element to be painted; * Returns: * nothing. * Note: * Used for both regular and preformatted text. *****/ static void DrawText(XmHTMLWidget html, XmHTMLObjectTableElement data) { int width, ys, xs, nwords = data->n_words; XmHTMLWord *words = data->words; TWindow win = Toolkit_Widget_Window (html->html.work_area); TGC gc = html->html.gc; register int i; register XmHTMLWord *tmp; TFontStruct *my_font; if(!nwords) return; #if 0 /* check for background color */ if(data->bg != html->html.body_bg) { xs = data->x - html->html.scroll_x; ys = data->y - html->html.scroll_y; Toolkit_Set_Foreground(dpy, gc, data->bg); Toolkit_Fill_Rectangle(dpy, win, gc, xs, ys - words[0].font->xfont->ascent, data->width, data->height); } #endif /* only need to set this once */ Toolkit_Set_Font (dpy, gc, words [0].font->xfont); my_font = words [0].font->xfont; Toolkit_Set_Foreground(dpy, gc, data->fg); for(i = 0 ; i < nwords; i++) { tmp = &words[i]; /* * When any of the two cases below is true, the text at the current * position is outside the exposed area. Not doing this check would * cause a visible flicker of the screen when scrolling: the entire * line would be repainted, even the invisible text. * And we sure don't want to render any linebreaks, looks pretty ugly. * (this test is a lot cheaper than doing ``invisible'' rendering) */ if((html->html.paint_y > tmp->y + tmp->height || html->html.paint_height < tmp->y) || (html->html.paint_x > tmp->x + tmp->width || html->html.paint_width < tmp->x) || tmp->type == OBJ_BLOCK) { #ifdef DEBUG if(html->html.paint_y > tmp->y + tmp->height || html->html.paint_height < tmp->y) _XmHTMLDebug(16, ("paint.c: DrawText, skipping %s, outside " "vertical range.\n", tmp->word)); else if(tmp->type != OBJ_BLOCK) _XmHTMLDebug(16, ("paint.c: DrawText, skipping %s, outside " "horizontal range.\n", tmp->word)); else _XmHTMLDebug(16, ("paint.c: DrawText, skipping break.\n")); #endif continue; } xs = tmp->x - html->html.scroll_x; ys = tmp->y - html->html.scroll_y; Toolkit_Draw_String (dpy, win, gc, xs, ys, tmp->word, tmp->len, my_font); if(tmp->line_data & LINE_UNDER) { int dy; /* * vertical position for underline, barely connects with the * underside of the ``deepest'' character. */ dy = ys + tmp->base->font->ul_offset; AdjustWordWidth; Toolkit_Set_Line_Attributes(dpy, gc, tmp->base->font->ul_thickness, (tmp->line_data & LINE_SOLID ? TLineSolid : TLineDoubleDash), TCapButt, TJoinBevel); Toolkit_Draw_Line(dpy, win, gc, xs, dy, xs + width, dy); if(tmp->line_data & LINE_DOUBLE) Toolkit_Draw_Line(dpy, win, gc, xs, dy+2, xs + width, dy+2); } if(tmp->line_data & LINE_STRIKE) { int dy; /* strikeout line is somewhere near the middle of a line */ dy = ys - tmp->base->font->st_offset; AdjustWordWidth; Toolkit_Set_Line_Attributes(dpy, gc, tmp->base->font->st_thickness, TLineSolid, TCapButt, TJoinBevel); Toolkit_Draw_Line(dpy, win, gc, xs, dy, xs + width, dy); } } } /***** * Name: DrawAnchor * Return Type: void * Description: main text anchor renderer. * In: * html: XmHTMLWidget id; * data: anchor to be painted; * Returns: * nothing. * Note: * This routine handles all textual anchor stuff. It paints anchors according * to the selected anchor style and (optionally) performs anchor highlighting. * Image anchors are rendered by DrawImageAnchor. *****/ static void DrawAnchor(XmHTMLWidget html, XmHTMLObjectTableElement data) { int x, xs, y, ys, width, start, nwords = 0; TWindow win = Toolkit_Widget_Window(html->html.work_area); TGC gc = html->html.gc; XmHTMLfont *font; register int i, j; XmHTMLWord **all_words = NULL, *words = NULL, *tmp; XmHTMLObjectTableElement a_start, a_end, temp; /* pick up the real start of this anchor */ for(a_start = data; a_start && a_start->anchor == data->anchor; a_start = a_start->prev); /* sanity, should never happen */ if(a_start == NULL) { _XmHTMLWarning(__WFUNC__(html, "DrawAnchor"), "Internal Error: " "could not locate anchor starting point!"); return; } /* previous loop always walks back one to many */ a_start = a_start->next; /* pick up the real end of this anchor and count the words */ for(a_end = a_start, nwords = 0; a_end != NULL && a_end->anchor == a_start->anchor; a_end = a_end->next) { /* ignore image words, they get handled by DrawImageAnchor. */ if(!(a_end->text_data & TEXT_IMAGE) && !(a_end->text_data & TEXT_BREAK)) nwords += a_end->n_words; } _XmHTMLDebug(16, ("paint.c: DrawAnchor, anchor contains %i words\n", nwords)); /* sanity check */ if(!nwords) return; /* fix 01/30/97-01, kdh */ /* * put all anchor words into an array if this anchor spans multiple * objects (as can be the case with font changes within an anchor) * If this isn't the case, just use the words of the current data * object. */ if(a_start->next != a_end) { _XmHTMLDebug(16, ("paint.c: DrawAnchor, allocating a word array for " "%i words\n", nwords)); all_words = (XmHTMLWord**)calloc(nwords, sizeof(XmHTMLWord*)); i = 0; for(temp = a_start; temp != a_end; temp = temp->next) { /* ignore image words, they get handled by DrawImageAnchor. */ if(!(temp->text_data & TEXT_IMAGE) && !(temp->text_data & TEXT_BREAK)) { for(j = 0 ; j < temp->n_words; j++) all_words[i++] = &(temp->words[j]); } } words = NULL; } else { _XmHTMLDebug(16, ("paint.c: DrawAnchor, not allocating a word array, " "all words belong to the same object.\n")); words = data->words; } /* * this is used for drawing the bounding rectangle of an anchor. * When an anchor is encountered, width is used to compute the total * width of a rectangle surrounding all anchor words on the same line. * The bounding rectangle drawn extends a little bit to the left and * right of the anchor. */ width = (words ? words[0].width : all_words[0]->width); /* extend to the left */ x = (words ? words[0].x : all_words[0]->x) - html->html.scroll_x - 2; y = (words ? words[0].y : all_words[0]->y) - html->html.scroll_y; i = start = 0; do { tmp = (words ? &words[i] : all_words[i]); /* anchors are always painted */ xs = tmp->x - html->html.scroll_x; ys = tmp->y - html->html.scroll_y; /* baseline font */ font = tmp->base->font; _XmHTMLFullDebug(16, ("paint.c: painting anchor, x = %i, y = %i\n", tmp->x, tmp->y)); /* compute total width of all words on this line */ if(ys == y) width = xs + tmp->width - x; /* + 2; */ /* extend to the right if this word has a trailing space */ width += (tmp->spacing & TEXT_SPACE_TRAIL ? 2 : 0); if(ys != y || i == nwords-1) { /***** * Anchor painting. We first make the distinction between documents * with and without a body image. * Then we check if we need to paint the anchors as pushbuttons * and then we check if we are to perform anchor highlighting. *****/ if(html->html.body_image == NULL) { /***** * No body image present. * Check if we are to paint the anchors as pushbuttons. *****/ if(html->html.anchor_buttons) { if(html->html.highlight_on_enter) { /* can only happen if XmNhighlightOnEnter is set */ if(data->anchor_state == ANCHOR_INSELECT) { /* paint button highlighting */ Toolkit_Fill_Rectangle (dpy, win, Toolkit_StyleGC_Highlight(html), x, y - font->xfont->ascent, width, font->lineheight); /* and draw as unselected */ DrawAnchorButton(html, x, y - font->xfont->ascent, width, font->lineheight, Toolkit_StyleGC_TopShadow(html), Toolkit_StyleGC_BottomShadow(html)); } else if(data->anchor_state == ANCHOR_SELECTED) { /* paint button highlighting */ Toolkit_Fill_Rectangle(dpy, win, Toolkit_StyleGC_Highlight(html), x, y - font->xfont->ascent, width, font->lineheight); /* and draw as selected */ DrawAnchorButton(html, x, y - font->xfont->ascent, width, font->lineheight, Toolkit_StyleGC_BottomShadow(html), Toolkit_StyleGC_TopShadow(html)); } else /* button is unselected */ { /* restore correct background */ Toolkit_Set_Foreground(dpy, gc, html->html.body_bg); Toolkit_Fill_Rectangle(dpy, win, gc, x, y - font->xfont->ascent, width, font->lineheight); /* and draw as unselected */ DrawAnchorButton(html, x, y - font->xfont->ascent, width, font->lineheight, Toolkit_StyleGC_TopShadow(html), Toolkit_StyleGC_BottomShadow(html)); } } else /* either selected or unselected */ { /* draw button as selected */ if(data->anchor_state == ANCHOR_SELECTED) DrawAnchorButton(html, x, y - font->xfont->ascent, width, font->lineheight, Toolkit_StyleGC_BottomShadow(html), Toolkit_StyleGC_TopShadow(html)); else /* draw button as unselected */ DrawAnchorButton(html, x, y - font->xfont->ascent, width, font->lineheight, Toolkit_StyleGC_TopShadow(html), Toolkit_StyleGC_BottomShadow(html)); } } else { /***** * No anchor buttons. Determine which color to use to paint * the bounding box around the anchor. * Note: without a body image, anchor highlighting is * achieved by painting the bounding box in the highlight * color. *****/ if(data->anchor_state == ANCHOR_INSELECT) Toolkit_Set_Foreground(dpy, gc, Toolkit_StyleColor_Highlight(html)); else if(data->anchor_state == ANCHOR_SELECTED) Toolkit_Set_Foreground(dpy, gc, html->html.anchor_activated_bg); else Toolkit_Set_Foreground(dpy, gc, html->html.body_bg); Toolkit_Fill_Rectangle(dpy, win, gc, x, y - font->xfont->ascent, width, font->lineheight); } /* set appropriate foreground color */ Toolkit_Set_Foreground(dpy, gc, data->anchor_state == ANCHOR_SELECTED ? html->html.anchor_activated_fg : data->fg); } else { /***** * We have a body image. Painting the buttons as above would * obliterate the part of the image under the anchor, so * if we are to perform button highlighting, we paint the * anchor's *text* in the highlight color. *****/ if(html->html.anchor_buttons) { if(data->anchor_state == ANCHOR_SELECTED) { /* draw as selected */ DrawAnchorButton(html, x, y - font->xfont->ascent, width, font->lineheight, Toolkit_StyleGC_BottomShadow(html), Toolkit_StyleGC_TopShadow(html)); } else /* button is unselected or being selected */ { /***** * button is unselected or being selected. In both * cases draw it as unselected. *****/ DrawAnchorButton(html, x, y - font->xfont->ascent, width, font->lineheight, Toolkit_StyleGC_TopShadow(html), Toolkit_StyleGC_BottomShadow(html)); } } /***** * no special stuff for anchor bounding box if we aren't * painting the anchors as buttons. *****/ /* set appropriate foreground color */ if(data->anchor_state == ANCHOR_INSELECT) Toolkit_Set_Foreground(dpy, gc, Toolkit_StyleColor_Highlight(html)); else if(data->anchor_state == ANCHOR_SELECTED) Toolkit_Set_Foreground(dpy, gc, html->html.anchor_activated_fg); else Toolkit_Set_Foreground(dpy, gc, data->fg); } /* * paint all text. Need to do it here since the XFillRect call * would obliterate any text painted before it. */ if(words) { for(j = start; j < i+1; j++) { TFontStruct *my_font = words [j].font->xfont; Toolkit_Set_Font (dpy, gc, my_font); Toolkit_Draw_String(dpy, win, gc, words[j].x - html->html.scroll_x, words[j].y - html->html.scroll_y, words[j].word, words[j].len, my_font); } } else { for(j = start; j < i+1; j++) { TFontStruct *my_font = all_words [j]->font->xfont; Toolkit_Set_Font (dpy, gc, all_words[j]->font->xfont); Toolkit_Draw_String(dpy, win, gc, all_words[j]->x - html->html.scroll_x, all_words[j]->y - html->html.scroll_y, all_words[j]->word, all_words[j]->len, my_font); } } /* Anchor buttons are never underlined, it looks ugly */ if(!html->html.anchor_buttons && (tmp->line_data & LINE_SOLID || tmp->line_data & LINE_DASHED)) { int dy = y + font->xfont->descent-2; Toolkit_Set_Line_Attributes(dpy, gc, 1, (tmp->line_data & LINE_SOLID ? TLineSolid : TLineDoubleDash), TCapButt, TJoinBevel); Toolkit_Draw_Line(dpy, win, gc, x+2, dy, x + width, dy); /* draw another line if requested */ if(tmp->line_data & LINE_DOUBLE) Toolkit_Draw_Line(dpy, win, gc, x+2, dy+2, x + width, dy+2); } if(tmp->line_data & LINE_STRIKE) { int dy = y - (int)(0.5*(font->xfont->ascent))+3; Toolkit_Set_Line_Attributes(dpy, gc, 1, TLineSolid, TCapButt, TJoinBevel); Toolkit_Draw_Line(dpy, win, gc, x+2, dy, x + width - 2, dy); } /* stupid hack to get the last word of a broken anchor right */ if(ys != y && i == nwords-1) i--; /* next word starts on another line */ width = tmp->width; start = i; x = xs-2; y = ys; } i++; } while(i != nwords); if(words == NULL) { _XmHTMLDebug(5, ("paint.c: DrawAnchor, freeing allocated word " "array.\n")); free(all_words); /* fix 05/26/97-01, kdh */ /***** * adjust current object data as we have now updated a number of * objects in one go. We must use prev as _XmHTMLPaint will advance * to a_end itself. *****/ data = (a_end ? a_end->prev : data); } } static void DrawImageAnchor(XmHTMLWidget html, XmHTMLObjectTableElement data) { int x, y, width, height; TWindow win = Toolkit_Widget_Window(html->html.work_area); TGC gc = html->html.gc; /* * this is used for drawing the bounding rectangle of an anchor. * When an anchor is encountered, width is used to compute the total * width of a rectangle surrounding all anchor words on the same line. * The bounding rectangle drawn extends a little bit to the left and * right of the anchor. */ width = data->words->width + 2; height = data->words->height + 2; /* extend to the left */ x = data->words->x - html->html.scroll_x - 1; /* and to the top */ y = data->words->y - html->html.scroll_y - 1; if(data->words->image->border) { /* add border offsets as well */ x -= data->words->image->border; y -= data->words->image->border; _XmHTMLFullDebug(16, ("paint.c: painting image anchor, x = %i, " "y = %i\n", data->x, data->y)); if(html->html.anchor_buttons) { TGC top, bottom; if(html->html.highlight_on_enter) { /* can only happen if XmNhighlightOnEnter is set */ if(data->anchor_state == ANCHOR_INSELECT) { /* paint button highlighting */ if(html->html.body_image == NULL && data->words->image->clip != None) Toolkit_Fill_Rectangle(dpy, win, Toolkit_StyleGC_Highlight(html), x, y , width, height); /* and draw as unselected */ top = Toolkit_StyleGC_TopShadow(html); bottom = Toolkit_StyleGC_BottomShadow(html); } else if(data->anchor_state == ANCHOR_SELECTED) { /* paint button highlighting */ if(html->html.body_image == NULL && data->words->image->clip != None) Toolkit_Fill_Rectangle(dpy, win, Toolkit_StyleGC_Highlight(html), x, y, width, height); /* and draw as selected */ top = Toolkit_StyleGC_BottomShadow(html); bottom = Toolkit_StyleGC_TopShadow(html); } else /* button is unselected */ { /* restore correct background */ if(html->html.body_image == NULL && data->words->image->clip != None) { Toolkit_Set_Foreground(dpy, gc, html->html.body_bg); Toolkit_Fill_Rectangle(dpy, win, gc, x, y, width, height); Toolkit_Set_Foreground(dpy, gc, html->html.body_fg); } /* and draw as unselected */ top = Toolkit_StyleGC_TopShadow(html); bottom = Toolkit_StyleGC_BottomShadow(html); } } else /* either selected or unselected */ { if(data->anchor_state == ANCHOR_SELECTED) { top = Toolkit_StyleGC_BottomShadow(html); bottom = Toolkit_StyleGC_TopShadow(html); } else { bottom = Toolkit_StyleGC_BottomShadow(html); top = Toolkit_StyleGC_TopShadow(html); } } DrawAnchorButton(html, x, y, width, height, top, bottom); } else { /* set line attribs */ Toolkit_Set_Line_Attributes(dpy, gc, data->words->image->border, TLineSolid, TCapButt, TJoinRound); /* draw background */ /* fix 04/03/97-01, kdh */ if(html->html.body_image == NULL) { if(data->anchor_state == ANCHOR_INSELECT) Toolkit_Set_Foreground(dpy, gc, Toolkit_StyleColor_Highlight(html)); else if(data->anchor_state == ANCHOR_SELECTED) Toolkit_Set_Foreground(dpy, gc, html->html.anchor_activated_bg); else Toolkit_Set_Foreground(dpy, gc, html->html.body_bg); Toolkit_Fill_Rectangle(dpy, win, gc, x, y, width, height); } /* draw lines */ Toolkit_Set_Foreground(dpy, gc, data->anchor_state == ANCHOR_SELECTED ? html->html.anchor_activated_fg : html->html.anchor_fg); Toolkit_Draw_Rectangle(dpy, win, gc, x, y, width, height); } } /* paint the alt text if images are disabled */ if(!html->html.images_enabled) { TFontStruct *my_font; my_font = data->words->font->xfont; Toolkit_Set_Font(dpy, gc, my_font); Toolkit_Set_Foreground(dpy, gc, data->anchor_state == ANCHOR_SELECTED ? html->html.anchor_activated_fg : html->html.anchor_fg); /* put text inside bounding rectangle */ x += data->words->image->width + 4; y += data->words->image->height/2 + 4; Toolkit_Draw_String (dpy, win, gc, x, y, data->words->word, data->words->len, my_font); } } /* * To prevent racing conditions, we must first remove an * existing timeout proc before we add a new one. */ #ifdef WITH_MOTIF #define REMOVE_TIMEOUTPROC(IMG) { \ if(IMG->proc_id) \ { \ _XmHTMLDebug(16, ("paint.c: DrawImage, removing animation %s " \ "timeout\n", IMG->url)); \ XtRemoveTimeOut(IMG->proc_id); \ IMG->proc_id = None; \ } \ } #else #define REMOVE_TIMEOUTPROC(IMG) { \ if (IMG->proc_id == None) { gtk_timeout_remove (IMG->proc_id); IMG->proc_id = None; } } #endif #ifdef WITH_MOTIF static void TimerCB(XtPointer data, XtIntervalId *id) #else int TimerCB(gpointer data) #endif { XmHTMLImage *image = (XmHTMLImage*)data; /* freeze animation at current frame */ if(image->html->html.freeze_animations) { REMOVE_TIMEOUTPROC(image); #ifdef WITH_MOTIF return; #else return TIdleKeep; #endif } image->options |= IMG_FRAMEREFRESH; _XmHTMLDrawImage(image->html, image->owner, 0, True); #ifndef WITH_MOTIF return TIdleRemove; #endif } #ifdef WITH_MOTIF #define RESET_GC(MYGC) { \ values.clip_mask = None; \ values.clip_x_origin = 0; \ values.clip_y_origin = 0; \ valuemask = GCClipMask | GCClipXOrigin | GCClipYOrigin; \ XChangeGC(dpy, MYGC, valuemask, &values); \ } #else #define RESET_GC(MYGC) { \ gdk_gc_set_clip_origin (MYGC, 0, 0); \ gdk_gc_set_clip_mask (MYGC, NULL); } #endif #define GET_TILE_OFFSETS(TSX,TSY) { \ int tile_width, tile_height, x_dist, y_dist; \ int ntiles_x, ntiles_y; \ int x_offset, y_offset, xd, yd; \ /* adjust for logical screen offsets */ \ xd = xs + fx; /* x-pos relative to upper-left screen corner */ \ yd = ys + fy; /* y-pos relative to upper-left screen corner */ \ tile_width = html->html.body_image->width; \ tile_height = html->html.body_image->height; \ x_dist = html->html.scroll_x + xd; /* total distance covered so far */ \ y_dist = html->html.scroll_y + yd; /* total distance covered so far */ \ ntiles_x = (int)(x_dist/tile_width); /* no of horizontal tiles */ \ ntiles_y = (int)(y_dist/tile_height); /* no of vertical tiles */ \ x_offset = x_dist - (ntiles_x * tile_width); \ y_offset = y_dist - (ntiles_y * tile_height); \ TSX = xd - x_offset; \ TSY = yd - y_offset; \ } /***** * Name: DrawFrame * Return Type: void * Description: animation driver, does frame disposal and renders a new * frame * In: * w: XmHTMLWidget id * image: image data * xs: absolute screen x-coordinate * ys: absolute screen y-coordinate * Returns: * nothing * Note: * Complex animations * ------------------ * Instead of drawing into the window directly, we draw into an internal * pixmap and blit this pixmap to the screen when all required processing * has been done, which is a lot faster than drawing on the screen directly. * Another advantage of this approach is that we always have a current state * available which can be used when an animation is scrolled on and off * screen (frame dimensions differ from logical screen dimensions or a * disposal method other than XmIMAGE_DISPOSE_NONE is to be used). * * Easy animations * --------------- * Each frame is blitted to screen directly, only processing done is using a * possible clipmask (frame dimensions equal to logical screen dimensions and * a disposal method of XmIMAGE_DISPOSE_NONE). *****/ static void DrawFrame(XmHTMLWidget html, XmHTMLImage *image, int xs, int ys) { int idx, width = 0, height = 0, fx, fy; TWindow win = Toolkit_Widget_Window(html->html.work_area); TGC gc = html->html.gc; _XmHTMLDebug(16, ("paint.c: DrawFrame, animation %s, frame %i, " "(x = %i, y = %i)\n", image->url, image->current_frame, xs, ys)); /* first reset the gc */ RESET_GC(gc); /* * First check if we are running this animation internally. If we aren't * we have a simple animation of which each frame has the same size and * no disposal method has been specified. This type of animations are blit * to screen directly. */ if(!ImageHasState(image)) { /* index of current frame */ idx = image->current_frame; width = image->frames[idx].w; height = image->frames[idx].h; /* can happen when a frame falls outside the logical screen area */ if(image->frames[idx].pixmap != None) { /* plug in the clipmask */ if(image->frames[idx].clip) { #ifdef WITH_MOTIF values.clip_mask = image->frames[idx].clip; values.clip_x_origin = xs; values.clip_y_origin = ys; valuemask = GCClipMask | GCClipXOrigin | GCClipYOrigin; XChangeGC(dpy, gc, valuemask, &values); #else gdk_gc_set_clip_origin (gc, xs, ys); gdk_gc_set_clip_mask (gc, image->frames [idx].clip); #endif } /* blit frame to screen */ Toolkit_Copy_Area (dpy, image->frames[idx].pixmap, win, gc, 0, 0, width, height, xs, ys); } /* * Jump to frame updating when we are not triggered * by an exposure, otherwise just return. */ if(ImageFrameRefresh(image)) goto nextframe; return; } /* * If DrawFrame was triggered by an exposure, just blit current animation * state to screen and return immediatly. */ if(!ImageFrameRefresh(image)) { Toolkit_Copy_Area (dpy, image->pixmap, win, gc, 0, 0, image->width, image->height, xs, ys); return; } /***** * If we get here we are running the animation internally. First check the * disposal method and update the current state accordingly *before* * putting the next frame on the display. * Pixmap can be None if a frame falls outside the logical screen area. * idx is the index of the previous frame (the frame that is currently * being displayed). *****/ idx = image->current_frame ? image->current_frame - 1 : image->nframes - 1; if(image->frames[idx].pixmap != None) { fx = image->frames[idx].x; fy = image->frames[idx].y; width = image->frames[idx].w; height = image->frames[idx].h; if(image->frames[idx].dispose == XmIMAGE_DISPOSE_BY_BACKGROUND) { _XmHTMLDebug(16, ("paint.c: DrawFrame, %s, disposing frame %i " "by background, x = %i, y = %i, %ix%i\n", image->url, idx, xs + fx, ys + fy, width, height)); /* we have a body image; get proper background tile offsets. */ if(html->html.body_image) { /* we have a body image, compute correct tile offsets */ int tsx, tsy; GET_TILE_OFFSETS(tsx,tsy); #ifdef WITH_MOTIF values.fill_style = FillTiled; values.tile = html->html.body_image->pixmap; values.ts_x_origin = tsx - xs; values.ts_y_origin = tsy - ys; _XmHTMLDebug(16, ("paint.c: DrawFrame: background disposal " "uses a tile with origin at (%i,%i)\n", tsx, tsy)); /* fix 06/18/97-02, kdh */ /* set gc values */ valuemask = GCTile | GCTileStipXOrigin | GCTileStipYOrigin | GCFillStyle ; XChangeGC(dpy, html->html.bg_gc, valuemask, &values); #else gdk_gc_set_fill (html->html.bg_gc, GDK_TILED); gdk_gc_set_tile (html->html.bg_gc, html->html.body_image->pixmap); gdk_gc_set_ts_origin (html->html.bg_gc, tsx-xs, tsx-ys); #endif /* paint it. */ Toolkit_Fill_Rectangle(dpy, image->pixmap, html->html.bg_gc, fx, fy, width, height); } /* * No body image, do a fillrect in background color. clipmasks * are ignored since we are already restoring to background! */ else { Toolkit_Set_Foreground(dpy, gc, html->html.body_bg); Toolkit_Fill_Rectangle(dpy, image->pixmap, gc, fx, fy, width, height); Toolkit_Set_Foreground(dpy, gc, html->html.body_fg); } } /* * no disposal method but we have a clipmask. Need to plug in * the background image or color. As we are completely overlaying * the current image with the new image, we can safely erase * the entire contents of the current state with the wanted * background, after which we use the clipmask to copy the requested * parts of the image on screen. * * Please note that this is *only* done for the very first frame * of such an animation. All other animations, whether they have a * clipmask or not, are put on top of this frame. Doing it for * other frames as well would lead to unwanted results as the * underlying portions of the animation would be replaced with the * current background, and thereby violating the none disposal * method logic. */ else if(image->frames[idx].dispose == XmIMAGE_DISPOSE_NONE && idx == 0 && image->frames[idx].clip != None) { /* we have a body image, compute correct tile offsets */ if(html->html.body_image) { int tsx, tsy; GET_TILE_OFFSETS(tsx,tsy); _XmHTMLDebug(16, ("paint.c: DrawFrame: background disposal " "uses a tile with origin at (%i,%i)\n", tsx, tsy)); #ifdef WITH_MOTIF /* update gc values */ values.fill_style = FillTiled; values.tile = html->html.body_image->pixmap; values.ts_x_origin = tsx - xs; values.ts_y_origin = tsy - ys; valuemask = GCTile | GCTileStipXOrigin | GCTileStipYOrigin | GCFillStyle ; XChangeGC(dpy, html->html.bg_gc, valuemask, &values); #else gdk_gc_set_fill (html->html.bg_gc, GDK_TILED); gdk_gc_set_tile (html->html.bg_gc, html->html.body_image->pixmap); gdk_gc_set_ts_origin (html->html.bg_gc, tsx-xs, tsy-ys); #endif /* do a fillrect to render the background image */ Toolkit_Fill_Rectangle(dpy, image->pixmap, html->html.bg_gc, fx, fy, width, height); /* * no need to reset the background gc, its only used for * overall background rendering. */ } else { /* do a plain fillrect in current background color */ Toolkit_Set_Foreground(dpy, gc, html->html.body_bg); Toolkit_Fill_Rectangle(dpy, image->pixmap, gc, fx, fy, width, height); Toolkit_Set_Foreground(dpy, gc, html->html.body_fg); } #ifdef WITH_MOTIF /* now plug in the clipmask */ values.clip_mask = image->frames[idx].clip; values.clip_x_origin = fx; values.clip_y_origin = fy; valuemask = GCClipMask | GCClipXOrigin | GCClipYOrigin; XChangeGC(dpy, gc, valuemask, &values); #else gdk_gc_set_clip_mask (gc, image->frames [idx].clip); gdk_gc_set_clip_origin (gc, fx, fy); #endif /* paint it. Use full image dimensions */ Toolkit_Copy_Area(dpy, image->frames[idx].pixmap, image->pixmap, gc, 0, 0, width, height, fx, fy); } /* dispose by previous (the only one to have a prev_state) */ else if(image->frames[idx].prev_state != None) { /* plug in the clipmask */ if(image->frames[idx].clip) { #ifdef WITH_MOTIF /* set gc values */ values.clip_mask = image->frames[idx].clip; values.clip_x_origin = fx; values.clip_y_origin = fy; valuemask = GCClipMask | GCClipXOrigin | GCClipYOrigin; XChangeGC(dpy, gc, valuemask, &values); #else gdk_gc_set_clip_mask (gc, image->frames [idx].clip); gdk_gc_set_clip_origin (gc, fx, fy); #endif } /* put previous screen state on current state */ Toolkit_Copy_Area(dpy, image->frames[idx].prev_state, image->pixmap, gc, 0, 0, width, height, fx, fy); } } /* reset gc */ RESET_GC(gc); /* index of current frame */ idx = image->current_frame; /* can happen when a frame falls outside the logical screen area */ if(image->frames[idx].pixmap != None) { fx = image->frames[idx].x; fy = image->frames[idx].y; width = image->frames[idx].w; height = image->frames[idx].h; /* * get current screen state if we are to dispose of this frame by the * previous state. The previous state is given by the current pixmap, * so we just create a new pixmap and copy the current one into it. * This is about the fastest method I can think of. */ if(image->frames[idx].dispose == XmIMAGE_DISPOSE_BY_PREVIOUS && image->frames[idx].prev_state == None) { TPixmap prev_state; TGC tmpGC; /* create pixmap that is to receive the image */ prev_state = Toolkit_Create_Pixmap(dpy, win, width, height, XCCGetDepth(html->html.xcc)); #ifdef WITH_MOTIF /* copy it */ tmpGC = XCreateGC(dpy, prev_state, 0, 0); XSetFunction(dpy, tmpGC, GXcopy); #else tmpGC = gdk_gc_new(prev_state); gdk_gc_set_function(tmpGC, GDK_COPY); #endif Toolkit_Copy_Area(dpy, image->pixmap, prev_state, tmpGC, fx, fy, width, height, 0, 0); /* and save it */ image->frames[idx].prev_state = prev_state; /* free and destroy */ Toolkit_GC_Free(dpy, tmpGC); } if(image->frames[idx].clip) { #ifdef WITH_MOTIF values.clip_mask = image->frames[idx].clip; values.clip_x_origin = fx; values.clip_y_origin = fy; valuemask = GCClipMask | GCClipXOrigin | GCClipYOrigin; XChangeGC(dpy, gc, valuemask, &values); #else gdk_gc_set_clip_origin (gc, fx, fy); gdk_gc_set_clip_mask (gc, image->frames [idx].clip); #endif } Toolkit_Copy_Area(dpy, image->frames[idx].pixmap, image->pixmap, gc, 0, 0, width, height, fx, fy); /* reset gc */ RESET_GC(gc); /* blit current state to screen */ Toolkit_Copy_Area(dpy, image->pixmap, win, gc, 0, 0, image->width, image->height, xs, ys); } nextframe: image->current_frame++; /* will get set again by TimerCB */ image->options &= ~(IMG_FRAMEREFRESH); if(image->current_frame == image->nframes) { image->current_frame = 0; /* * Sigh, when an animation is running forever (loop_count == 0) and * some sucker is keeping XmHTML up forever, chances are that we *can* * exceed INT_MAX. Since some systems don't wrap their integers properly * when their value exceeds INT_MAX, we can't keep increasing the * current loop count forever since this *can* lead to a crash (which * is a potential security hole). To prevent this from happening, we * *only* increase current_loop when run this animation a limited * number of times. */ if(image->loop_count) { image->current_loop++; /* * If the current loop count matches the total loop count, depromote * the animation to a regular image so the next time the timer * callback is activated we will enter normal image processing. */ if(image->current_loop == image->loop_count) image->options &= ~(IMG_ISANIM); } } /* * To prevent racing conditions, we must first remove an existing * timeout proc before adding a new one. */ REMOVE_TIMEOUTPROC(image); #ifdef WITH_MOTIF image->proc_id = XtAppAddTimeOut(image->context, image->frames[idx].timeout, TimerCB, image); #else image->proc_id = gtk_timeout_add (image->frames [idx].timeout, TimerCB, image); #endif _XmHTMLDebug(16, ("paint.c: DrawFrame end\n")); } /***** * Name: _XmHTMLDrawImage * Return Type: void * Description: image refresher. * In: * w: XmHTMLWidget id * data: Object data. * y_offset: vertical offset for screen copying; * from_timerCB: true when called from the timeout proc. * Returns: * nothing * Note: * this is a funny routine: it does plain images as well as * animations. Animations with a loop count of zero will loop * forever. Other animations will loop their counts and when that * has been reached they are depromoted to regular images. * The only way to restore animations with a loop count to animations * again is to reload them (XmHTMLImageUpdate). *****/ void _XmHTMLDrawImage(XmHTMLWidget html, XmHTMLObjectTableElement data, int y_offset, Boolean from_timerCB) { int xs, ys; XmHTMLImage *image; Display *dpy; TWindow win; TGC gc; /* sanity check */ if((image = data->words->image) == NULL) return; dpy = Toolkit_Display(html->html.work_area); win = Toolkit_Widget_Window(html->html.work_area); gc = (ImageIsProgressive(image) ? html->html.plc_gc : html->html.gc); /* compute correct image offsets */ xs = data->words->x - html->html.scroll_x; ys = data->words->y - html->html.scroll_y; _XmHTMLDebug(16, ("paint.c: DrawImage start, x = %i, y = %i\n", data->x, data->y)); /* * animation frames should be repainted if the animation is somewhere * in the visible area. */ if(ImageFrameRefresh(image)) { if(xs + image->width < 0 || xs > html->html.work_width || ys + image->height < 0 || ys > html->html.work_height) { _XmHTMLDebug(16, ("paint.c: DrawImage end, animation %s not in " "visible area.\n", image->url)); REMOVE_TIMEOUTPROC(image); return; } } /* * Only do this when we are repainting this image as a result of an * exposure. */ if(!from_timerCB) { /* anchors are always repainted if they are visible */ /* fix 03/25/97-01, kdh */ if(data->text_data & TEXT_ANCHOR) { if(xs + image->width > 0 && xs < html->html.work_width && ys + image->height > 0 && ys < html->html.work_height) DrawImageAnchor(html, data); } else { /* * When any of the two cases below is true, the image is not in the * exposed screen area. Not doing this check would cause a visible * flicker of the screen when scrolling: the entire image would be * repainted even if it is not visible. */ if(html->html.paint_y > data->words->y + image->height || html->html.paint_height < data->words->y) { _XmHTMLDebug(16, ("paint.c: DrawImage end, out of vertical " "range.\n")); return; } if(html->html.paint_x > data->words->x + image->width || html->html.paint_width < data->words->x) { _XmHTMLDebug(16, ("paint.c: DrawImage end, out of horizontal " "range.\n")); return; } } } /* * If this is an animation, paint next frame or restore current * state when we are scrolling this animation on and off screen. */ if(ImageIsAnim(image)) DrawFrame(html, image, xs, ys); else if(image->pixmap != None) { /* put in clipmask */ if(image->clip) { #ifdef WITH_MOTIF /* set gc values */ values.clip_mask = image->clip; values.clip_x_origin = xs; values.clip_y_origin = ys; valuemask = GCClipMask | GCClipXOrigin | GCClipYOrigin; XChangeGC(dpy, gc, valuemask, &values); #else gdk_gc_set_clip_mask (gc, image->clip); gdk_gc_set_clip_origin (gc, xs, ys); #endif } /* copy to screen */ Toolkit_Copy_Area(dpy, image->pixmap, win, gc, 0, y_offset, image->width, image->height, xs, ys + y_offset); } /* reset gc */ RESET_GC(gc); /***** * Paint the alt text if images are disabled or when this image is * delayed. *****/ if((!html->html.images_enabled || (image->html_image && ImageInfoDelayed(image->html_image))) && !(data->text_data & TEXT_ANCHOR)) { TFontStruct *my_font = data->words->font->xfont; Toolkit_Set_Font(dpy, gc, my_font); Toolkit_Set_Foreground(dpy, gc, html->html.body_fg); /* put text inside bounding rectangle */ xs += image->width + 4; ys += image->height/2 + 4; Toolkit_Draw_String(dpy, win, gc, xs, ys, data->words->word, data->words->len, my_font); } /* check if we have to draw the imagemap bounding boxes */ if(image->map_type == XmMAP_CLIENT && html->html.imagemap_draw) _XmHTMLDrawImagemapSelection(html, image); _XmHTMLDebug(16, ("paint.c: DrawImage end\n")); } /***** * Name: DrawRule * Return Type: void * Description: paints a horizontal rule. * In: * html: XmHTMLWidget id; * data: element data; * Returns: * nothing. * Note: * Rules that had their noshade attribute set are identiefied by having * a non-zero y_offset field in the data. We support a color attribute * in this case as well, so colored rules are possible. They are also * possible if a hr is in the proper context: one of the extensions * supported by XmHTML is a color attribute on the DIV tag. *****/ static void DrawRule(XmHTMLWidget html, XmHTMLObjectTableElement data) { int dy; TWindow win = Toolkit_Widget_Window(html->html.work_area); TGC gc; int xs, ys; /* * recompute rule layout if we are auto-sizing. * Needs to be done since the formatted_width is only known once * the entire document has been laid out. */ if(html->html.resize_width) { int x; int width = html->html.work_width - html->html.margin_width; /* horizontal offset */ x = html->html.margin_width + data->ident; /* See if we have an width specification */ if(data->len != 0) { if(data->len < 0) /* % spec */ width *= (float)(-1*data->len/100.); else /* pixel spec, cut if wider than available */ width = (data->len > width ? width : data->len); /* alignment is only honored if there is a width spec */ switch(data->halign) { case XmHALIGN_RIGHT: x = html->html.margin_width + html->html.work_width - width; break; case XmHALIGN_CENTER: x = html->html.margin_width + (html->html.work_width - width - html->html.margin_width)/2; default: /* shutup compiler */ break; } } /* Save updated position and width */ data->x = x; data->width = width; } xs = data->x - html->html.scroll_x; /* vertical offset */ #if 0 dy = (int)(0.75*(html->html.default_font->height)); #else dy = 0; #endif ys = data->y - html->html.scroll_y; if(data->height) { if(data->y_offset) /* noshade */ { gc = html->html.gc; Toolkit_Set_Line_Attributes(dpy, gc, 1, TLineSolid, TCapButt, TJoinBevel); Toolkit_Set_Foreground(dpy, gc, data->fg); Toolkit_Fill_Rectangle(dpy, win, gc, xs, ys + dy, data->width, data->height); } else { if (data->fg != html->html.body_fg) _XmHTMLRecomputeShadowColors(html, data->fg); Toolkit_Draw_Shadows(html, Toolkit_StyleGC_TopShadow(html), Toolkit_StyleGC_BottomShadow(html), xs, ys+dy, data->width, data->height, 1, XmSHADOW_IN); if (data->fg != html->html.body_fg) _XmHTMLRecomputeShadowColors(html, html->html.body_bg); } } else if(data->y_offset) /* noshade */ { gc = html->html.gc; Toolkit_Set_Line_Attributes(tka->dpy, gc, 1, TLineSolid, TCapButt, TJoinBevel); Toolkit_Set_Foreground(dpy, gc, data->fg); Toolkit_Draw_Line(dpy, win, gc, xs, ys + dy, xs + data->width, ys + dy); Toolkit_Draw_Line(dpy, win, gc, xs, ys + dy + 1, xs + data->width, ys + dy + 1); } else { if(data->fg != html->html.body_fg) _XmHTMLRecomputeShadowColors(html, data->fg); Toolkit_Draw_Shadows(html, Toolkit_StyleGC_TopShadow(html), Toolkit_StyleGC_BottomShadow(html), xs, ys + dy, data->width, 2, 1, XmSHADOW_IN); if(data->fg != html->html.body_fg) _XmHTMLRecomputeShadowColors(html, html->html.body_bg); } } static void DrawBullet(XmHTMLWidget html, XmHTMLObjectTableElement data) { TWindow win = Toolkit_Widget_Window(html->html.work_area); TGC gc = html->html.gc; int ys, xs; /* reset colors, an anchor might have been drawn before */ Toolkit_Set_Foreground(dpy, gc, data->fg); Toolkit_Set_Line_Attributes(dpy, gc, 1, TLineSolid, TCapButt, TJoinBevel); xs = data->x - html->html.scroll_x; ys = data->y - html->html.scroll_y; switch(data->marker) { case XmMARKER_DISC: Toolkit_Fill_Arc(dpy, win, gc, xs - 2*data->width, ys - data->height, data->width, data->width, 0, 23040); break; case XmMARKER_SQUARE: Toolkit_Draw_Rectangle(dpy, win, gc, xs - 2*data->width, ys - data->height, data->width, data->width); break; case XmMARKER_CIRCLE: Toolkit_Draw_Arc(dpy, win, gc, xs - 2*data->width, ys - data->height, data->width, data->width, 0, 23040); break; default: { TFontStruct *my_font; my_font = html->html.default_font->xfont; Toolkit_Set_Font(dpy, gc, my_font); Toolkit_Draw_String(dpy, win, gc, xs - data->width, ys, data->text, data->len, my_font); break; } } } static void DrawAnchorButton(XmHTMLWidget html, int x, int y, Dimension width, Dimension height, TGC top_shadow_GC, TGC bottom_shadow_GC) { TWindow win = Toolkit_Widget_Window(html->html.work_area); /* top & left border */ Toolkit_Draw_Line(dpy, win, top_shadow_GC, x, y, x + width - 1, y); Toolkit_Draw_Line(dpy, win, top_shadow_GC, x, y + 1, x, y + height - 1); /* bottom & right border */ Toolkit_Draw_Line(dpy, win, bottom_shadow_GC, x, y + height, x + width, y + height); Toolkit_Draw_Line(dpy, win, bottom_shadow_GC, x + width, y, x + width, y + height - 1); } static XmHTMLObjectTableElement DrawTable(XmHTMLWidget html, XmHTMLObjectTableElement start, XmHTMLObjectTableElement data_end) { XmHTMLTable *table; TableRow *row = NULL; TableCell *cell = NULL; int nrows, ncols, i, j; /* pick up table data */ table = start->table; if(table == NULL) return(start); /***** * The first table in a stack of tables contains all data for all * table childs it contains. The first table child is the master * table itself. So when a table doesn't have a child table it *is* * a child table itself and thus we should add the left offset * to the initial horizontal position. *****/ if(table->childs) table = &(table->childs[0]); /* only render table border if we have to */ if(table->properties->framing != TFRAME_VOID) DrawTableBorder(html, table); nrows = table->nrows; ncols = table->ncols; for(i = 0; i < nrows; i++) { row = &(table->rows[i]); for(j = 0; j < row->ncells; j++) { cell = &(row->cells[j]); /* only draw something if it falls in the exposure area */ if((html->html.paint_y > cell->owner->y + cell->owner->height || html->html.paint_height < cell->owner->y) || (html->html.paint_x > cell->owner->x + cell->owner->width || html->html.paint_width < cell->owner->x)) continue; /***** * Render the cell if it has a background color/image or when * we have to draw borders. *****/ /* check if we have to draw a border */ if(cell->properties->bg != html->html.body_bg || cell->properties->bg_image != NULL || cell->properties->ruling != TRULE_NONE) DrawCellFrame(html, cell); DrawCellContent(html, cell->start, cell->end); } } /* sanity */ if(table->end && data_end) return(table->end->y < data_end->y ? table->end->prev : data_end->prev); return(table->end ? table->end->prev : data_end); } static void DrawCellContent(XmHTMLWidget html, XmHTMLObjectTableElement start, XmHTMLObjectTableElement end) { XmHTMLObjectTableElement temp; temp = start; _XmHTMLDebug(16, ("paint.c: DrawCellContent, table_start: x = %i, " "y = %i\n", start->x, start->y)); while(temp && temp != end) { _XmHTMLDebug(16, ("paint.c: DrawCellContent, checking object %s\n", temp->object ? temp->object->element : "")); switch(temp->object_type) { case OBJ_TEXT: case OBJ_PRE_TEXT: /* * First check if this is an image. DrawImage will render * an image as an anchor if required. */ if(temp->text_data & TEXT_IMAGE) _XmHTMLDrawImage(html, temp, 0, False); else { /* form scrolling gets handled by formScroll in XmHTML.c */ if(temp->text_data & TEXT_FORM) break; else { if(temp->text_data & TEXT_ANCHOR) DrawAnchor(html, temp); else DrawText(html, temp); } } break; case OBJ_BULLET: DrawBullet(html, temp); break; case OBJ_HRULE: DrawRule(html, temp); break; case OBJ_TABLE: /* nested tables, hehehe */ (void)DrawTable(html, temp, end); break; case OBJ_TABLE_FRAME: case OBJ_IMG: case OBJ_APPLET: case OBJ_BLOCK: case OBJ_NONE: _XmHTMLDebug(16, ("paint.c: DrawTable, skipping undrawable " "object %s\n", temp->object->element)); break; default: _XmHTMLWarning(__WFUNC__(html, "DrawTable"), "Unknown object type!"); } temp = temp->next; } _XmHTMLDebug(16, ("paint.c: DrawCellContent End\n")); } /***** * Name: DrawCellFrame * Return Type: void * Description: renders a frame around a table cell. Optionally fills it * with a background color or image. * In: * html: XmHTMLWidget id; * cell: cell to be framed; * Returns: * nothing. *****/ static void DrawCellFrame(XmHTMLWidget html, TableCell *cell) { XmHTMLObjectTableElement data = cell->owner; TWindow win = Toolkit_Widget_Window(html->html.work_area); TGC gc; int xs, ys, xoff, yoff, width, height; Byte rule = DRAW_BOX; /* which sides do we have to render? */ switch(cell->properties->ruling) { case TRULE_NONE: /* no rules, only bg color/image will be done */ rule = DRAW_NONE; break; case TRULE_GROUPS: /* only horizontal rules */ case TRULE_ROWS: /* only horizontal rules */ rule = DRAW_LEFT|DRAW_RIGHT; break; case TRULE_COLS: /* only vertical rules */ rule = DRAW_TOP|DRAW_BOTTOM; break; case TRULE_ALL: /* all rules */ break; } xoff = cell->parent->parent->hmargin; width = data->width - xoff; yoff = data->font->xfont->ascent; height = data->height - cell->parent->parent->vmargin; /* initial, absolute, positions */ xs = data->x + xoff; ys = data->y - yoff; /* Correct absolute positions so only the exposed region is updated */ if(xs < html->html.paint_x) { /* origin too far left */ width -= (html->html.paint_x - xs); xs = html->html.paint_x; rule &= ~DRAW_LEFT; } if(xs + width > html->html.paint_width) { /* width too far right */ width = html->html.paint_width - xs; rule &= ~DRAW_RIGHT; } if(ys < html->html.paint_y) { /* origin too high */ height -= (html->html.paint_y - ys); ys = html->html.paint_y; rule &= ~DRAW_TOP; } if(ys + height > html->html.paint_height) { /* height too low */ height = html->html.paint_height - ys; rule &= ~DRAW_BOTTOM; } if(height <= 0) return; /***** * Translate absolute coordinates to relative ones by substracting * the region that has been scrolled. *****/ xs -= html->html.scroll_x; ys -= html->html.scroll_y; /* Do we have a unique background color? */ if(cell->owner->bg != html->html.body_bg) { Toolkit_Set_Foreground(dpy, html->html.gc, data->bg); Toolkit_Fill_Rectangle(dpy, win, html->html.gc, xs, ys, width, height); } /* Do we have a background image? */ if(cell->properties->bg_image != NULL) { int tile_width, tile_height, x_dist, y_dist; int ntiles_x, ntiles_y, tsx, tsy; int x_offset, y_offset; XmHTMLImage *bg_image = cell->properties->bg_image; tile_width = bg_image->width; tile_height = bg_image->height; /* total distance covered so far */ x_dist = html->html.scroll_x + xs; y_dist = html->html.scroll_y + ys; /* no of horizontal tiles */ ntiles_x = (int)(x_dist/tile_width); ntiles_y = (int)(y_dist/tile_height); x_offset = x_dist - (ntiles_x * tile_width); y_offset = y_dist - (ntiles_y * tile_height); tsx = xs - x_offset; tsy = ys - y_offset; #if WITH_MOTIF values.fill_style = FillTiled; values.tile = bg_image->pixmap; values.ts_x_origin = tsx - xs; values.ts_y_origin = tsy - ys; /* set gc values */ valuemask = GCTile | GCTileStipXOrigin | GCTileStipYOrigin | GCFillStyle ; XChangeGC(dpy, html->html.bg_gc, valuemask, &values); #else gdk_gc_set_fill (html->html.bg_gc, GDK_TILED); gdk_gc_set_tile (html->html.bg_gc, bg_image->pixmap); gdk_gc_set_ts_origin (html->html.bg_gc, tsx -xs, tsy - ys); #endif /* paint it. */ Toolkit_Fill_Rectangle(dpy, win, html->html.bg_gc, xs, ys, width, height); } /* no border drawing if we don't have to */ if(cell->properties->border == 0) return; /* top & left border */ gc = Toolkit_StyleGC_BottomShadow (html); /* top & left border */ if(rule & DRAW_TOP) Toolkit_Fill_Rectangle(dpy, win, gc, xs, ys, width, 1); if(rule & DRAW_LEFT) Toolkit_Fill_Rectangle(dpy, win, gc, xs, ys, 1, height - 1); /* bottom & right border */ gc = Toolkit_StyleGC_TopShadow (html); if(rule & DRAW_BOTTOM) Toolkit_Fill_Rectangle(dpy, win, gc, xs + 1, ys + height - 1, width - 1, 1); if(rule & DRAW_RIGHT) Toolkit_Fill_Rectangle(dpy, win, gc, xs + width - 1, ys + 1, 1, height - 2); } /***** * Name: DrawTableBorder * Return Type: void * Description: renders a frame around a table. * In: * html: XmHTMLWidget id; * table: table for which a frame must be rendered; * Returns: * nothing. *****/ static void DrawTableBorder(XmHTMLWidget html, XmHTMLTable *table) { TWindow win = Toolkit_Widget_Window(html->html.work_area); TGC gc; int xs, ys; XmHTMLObjectTableElement data = table->owner; int bwidth, xoff, yoff, width, height; Byte rule = DRAW_BOX; bwidth = table->properties->border; xoff = bwidth + table->hmargin; width = data->width + 2 * xoff; yoff = bwidth + table->vmargin + data->font->xfont->ascent - 1; height = data->height + bwidth + table->vmargin; /* horizontal offset, taking scrolling into account */ xs = data->x - html->html.scroll_x; /* vertical offset, taking scrolling into account */ ys = data->y - html->html.scroll_y - yoff; _XmHTMLDebug(16, ("Drawing table border: x = %i, y = %i, width = %i, " "height = %i\n", xs, ys, width, height)); /* which sides do we have to render? */ switch(table->properties->framing) { case TFRAME_VOID: return; break; case TFRAME_ABOVE: rule = DRAW_TOP; break; case TFRAME_BELOW: rule = DRAW_BOTTOM; break; case TFRAME_LEFT: rule = DRAW_LEFT; break; case TFRAME_RIGHT: rule = DRAW_RIGHT; break; case TFRAME_HSIDES: rule = DRAW_LEFT|DRAW_RIGHT; break; case TFRAME_VSIDES: rule = DRAW_TOP|DRAW_BOTTOM; break; case TFRAME_BOX: case TFRAME_BORDER: break; } /* top & left border */ gc = Toolkit_StyleGC_TopShadow (html); /* top & left border */ if(rule & DRAW_TOP) Toolkit_Fill_Rectangle(dpy, win, gc, xs, ys, width, 1); if(rule & DRAW_LEFT) Toolkit_Fill_Rectangle(dpy, win, gc, xs, ys, 1, height-1); /* bottom & right border */ gc = Toolkit_StyleGC_BottomShadow(html); if(rule & DRAW_BOTTOM) Toolkit_Fill_Rectangle(dpy, win, gc, xs + 1, ys + height - 1, width - 1, 1); if(rule & DRAW_RIGHT) Toolkit_Fill_Rectangle(dpy, win, gc, xs + width - 1, ys + 1, 1, height-2); }