/* ** The main routine for the HTML widget for Tcl/Tk ** ** Copyright (C) 1997-2000 D. Richard Hipp ** ** 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., 59 Temple Place - Suite 330, ** Boston, MA 02111-1307, USA. ** ** Author contact information: ** drh@acm.org ** http://www.hwaci.com/drh/ */ #include #include #include #include #include "htmlwidget.h" /* #ifdef USE_TK_STUBS # include #endif */ /* ** This global variable is used for tracing the operation of ** the Html formatter. */ int HtmlTraceMask = 0; #ifdef __WIN32__ # define DEF_FRAME_BG_COLOR "SystemButtonFace" # define DEF_FRAME_BG_MONO "White" # define DEF_FRAME_CURSOR "" # define DEF_BUTTON_FG "SystemButtonText" # define DEF_BUTTON_HIGHLIGHT_BG "SystemButtonFace" # define DEF_BUTTON_HIGHLIGHT "SystemWindowFrame" #else # define DEF_FRAME_BG_COLOR "#d9d9d9" # define DEF_FRAME_BG_MONO "White" # define DEF_FRAME_CURSOR "" # define DEF_BUTTON_FG "Black" # define DEF_BUTTON_HIGHLIGHT_BG "#d9d9d9" # define DEF_BUTTON_HIGHLIGHT "Black" #endif /* ** Information used for argv parsing. */ static Tk_ConfigSpec configSpecs[] = { {TK_CONFIG_STRING, "-appletcommand", "appletCommand", "HtmlCallback", DEF_HTML_CALLBACK, Tk_Offset(HtmlWidget, zAppletCommand), 0}, {TK_CONFIG_BORDER, "-background", "background", "Background", DEF_HTML_BG_COLOR, Tk_Offset(HtmlWidget, border), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_BORDER, "-background", "background", "Background", DEF_HTML_BG_MONO, Tk_Offset(HtmlWidget, border), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_STRING, "-base", "base", "Base", "", Tk_Offset(HtmlWidget, zBase), 0}, {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL, (char *) NULL, 0, 0}, {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL, (char *) NULL, 0, 0}, {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", DEF_HTML_BORDER_WIDTH, Tk_Offset(HtmlWidget, borderWidth), 0}, {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", DEF_HTML_CURSOR, Tk_Offset(HtmlWidget, cursor), TK_CONFIG_NULL_OK}, {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection","ExportSelection", DEF_HTML_EXPORT_SEL, Tk_Offset(HtmlWidget, exportSelection), 0}, {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL, (char *) NULL, 0, 0}, {TK_CONFIG_STRING, "-fontcommand", "fontCommand", "FontCommand", DEF_HTML_CALLBACK, Tk_Offset(HtmlWidget, zFontCommand), 0}, {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground", DEF_HTML_FG, Tk_Offset(HtmlWidget, fgColor), 0}, {TK_CONFIG_STRING, "-formcommand", "formlCommand", "HtmlCallback", DEF_HTML_CALLBACK, Tk_Offset(HtmlWidget, zFormCommand), 0}, {TK_CONFIG_STRING, "-framecommand", "frameCommand", "HtmlCallback", DEF_HTML_CALLBACK, Tk_Offset(HtmlWidget, zFrameCommand), 0}, {TK_CONFIG_PIXELS, "-height", "height", "Height", DEF_HTML_HEIGHT, Tk_Offset(HtmlWidget, height), 0}, {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground", "HighlightBackground", DEF_HTML_HIGHLIGHT_BG, Tk_Offset(HtmlWidget, highlightBgColorPtr), 0}, {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", DEF_HTML_HIGHLIGHT, Tk_Offset(HtmlWidget, highlightColorPtr), 0}, {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness", "HighlightThickness", DEF_HTML_HIGHLIGHT_WIDTH, Tk_Offset(HtmlWidget, highlightWidth), 0}, {TK_CONFIG_STRING, "-hyperlinkcommand", "hyperlinkCommand", "HtmlCallback", DEF_HTML_CALLBACK, Tk_Offset(HtmlWidget, zHyperlinkCommand), 0}, {TK_CONFIG_STRING, "-imagecommand", "imageCommand", "HtmlCallback", DEF_HTML_CALLBACK, Tk_Offset(HtmlWidget, zGetImage), 0}, {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime", DEF_HTML_INSERT_OFF_TIME, Tk_Offset(HtmlWidget, insOffTime), 0}, {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime", DEF_HTML_INSERT_ON_TIME, Tk_Offset(HtmlWidget, insOnTime), 0}, {TK_CONFIG_STRING, "-isvisitedcommand", "isVisitedCommand", "HtmlCallback", DEF_HTML_CALLBACK, Tk_Offset(HtmlWidget, zIsVisited), 0}, {TK_CONFIG_PIXELS, "-padx", "padX", "Pad", DEF_HTML_PADX, Tk_Offset(HtmlWidget, padx), 0}, {TK_CONFIG_PIXELS, "-pady", "padY", "Pad", DEF_HTML_PADY, Tk_Offset(HtmlWidget, pady), 0}, {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", DEF_HTML_RELIEF, Tk_Offset(HtmlWidget, relief), 0}, {TK_CONFIG_STRING, "-resolvercommand", "resolverCommand", "HtmlCallback", DEF_HTML_CALLBACK, Tk_Offset(HtmlWidget, zResolverCommand), 0}, {TK_CONFIG_RELIEF, "-rulerelief", "ruleRelief","RuleRelief", "sunken", Tk_Offset(HtmlWidget, ruleRelief), 0}, {TK_CONFIG_STRING, "-scriptcommand", "scriptCommand", "HtmlCallback", "", Tk_Offset(HtmlWidget, zScriptCommand), 0}, {TK_CONFIG_COLOR, "-selectioncolor", "background", "Background", DEF_HTML_SELECTION_COLOR, Tk_Offset(HtmlWidget, selectionColor), 0}, {TK_CONFIG_RELIEF, "-tablerelief", "tableRelief","TableRelief", "raised", Tk_Offset(HtmlWidget, tableRelief), 0}, {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", DEF_HTML_TAKE_FOCUS, Tk_Offset(HtmlWidget, takeFocus), TK_CONFIG_NULL_OK}, {TK_CONFIG_COLOR, "-unvisitedcolor", "foreground", "Foreground", DEF_HTML_UNVISITED, Tk_Offset(HtmlWidget, newLinkColor), 0}, {TK_CONFIG_BOOLEAN, "-underlinehyperlinks", "underlineHyperlinks", "UnderlineHyperlinks", "1", Tk_Offset(HtmlWidget, underlineLinks), 0}, {TK_CONFIG_COLOR, "-visitedcolor", "foreground", "Foreground", DEF_HTML_VISITED, Tk_Offset(HtmlWidget, oldLinkColor), 0}, {TK_CONFIG_PIXELS, "-width", "width", "Width", DEF_HTML_WIDTH, Tk_Offset(HtmlWidget, width), 0}, {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", DEF_HTML_SCROLL_COMMAND, Tk_Offset(HtmlWidget, xScrollCmd), TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", DEF_HTML_SCROLL_COMMAND, Tk_Offset(HtmlWidget, yScrollCmd), TK_CONFIG_NULL_OK}, {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, (char *) NULL, 0, 0} }; /* ** Get a copy of the config specs. */ Tk_ConfigSpec *HtmlConfigSpec(void){ return configSpecs; } /* ** Find the width of the usable drawing area in pixels. If the window isn't ** mapped, use the size requested by the user. ** ** The usable drawing area is the area available for displaying rendered ** HTML. The usable drawing area does not include the 3D border or the ** padx and pady boundry within the 3D border. The usable drawing area ** is the size of the clipping window. */ int HtmlUsableWidth(HtmlWidget *htmlPtr){ int w; Tk_Window tkwin = htmlPtr->tkwin; if( tkwin && Tk_IsMapped(tkwin) ){ w = Tk_Width(tkwin) - 2*(htmlPtr->padx + htmlPtr->inset); TestPoint(0); }else{ w = htmlPtr->width; TestPoint(0); } return w; } /* ** Find the height of the usable drawing area in pixels. If the window isn't ** mapped, use the size requested by the user. ** ** The usable drawing area is the area available for displaying rendered ** HTML. The usable drawing area does not include the 3D border or the ** padx and pady boundry within the 3D border. The usable drawing area ** is the size of the clipping window. */ int HtmlUsableHeight(HtmlWidget *htmlPtr){ int h; Tk_Window tkwin = htmlPtr->tkwin; if( tkwin && Tk_IsMapped(tkwin) ){ h = Tk_Height(tkwin) - 2*(htmlPtr->pady + htmlPtr->inset); TestPoint(0); }else{ h = htmlPtr->height; TestPoint(0); } return h; } /* ** Compute a pair of floating point numbers that describe the current ** vertical scroll position. The first number is the fraction of ** the document that is off the top of the visible region and the second ** number is the fraction that is beyond the end of the visible region. */ void HtmlComputeVerticalPosition( HtmlWidget *htmlPtr, char *buf /* Write the two floating point values here */ ){ int actual; /* Size of the viewing area */ double frac1, frac2; actual = HtmlUsableHeight(htmlPtr); if( htmlPtr->maxY <= 0 ){ frac1 = 0.0; frac2 = 1.0; TestPoint(0); }else{ frac1 = (double)htmlPtr->yOffset/(double)htmlPtr->maxY; if( frac1 > 1.0 ){ frac1 = 1.0; TestPoint(0); }else if( frac1 < 0.0 ){ frac1 = 0.0; TestPoint(0); } frac2 = (double)(htmlPtr->yOffset+actual)/(double)htmlPtr->maxY; if( frac2 > 1.0 ){ frac2 = 1.0; TestPoint(0); }else if( frac2 < 0.0 ){ frac2 = 0.0; TestPoint(0); } } sprintf(buf,"%g %g",frac1, frac2); } /* ** Do the same thing for the horizontal direction */ void HtmlComputeHorizontalPosition( HtmlWidget *htmlPtr, char *buf /* Write the two floating point values here */ ){ int actual; /* Size of the viewing area */ double frac1, frac2; actual = HtmlUsableWidth(htmlPtr); if( htmlPtr->maxX <= 0 ){ frac1 = 0.0; frac2 = 1.0; TestPoint(0); }else{ frac1 = (double)htmlPtr->xOffset/(double)htmlPtr->maxX; if( frac1 > 1.0 ){ frac1 = 1.0; TestPoint(0); }else if( frac1 < 0.0 ){ frac1 = 0.0; TestPoint(0); } frac2 = (double)(htmlPtr->xOffset+actual)/(double)htmlPtr->maxX; if( frac2 > 1.0 ){ frac2 = 1.0; TestPoint(0); }else if( frac2 < 0.0 ){ frac2 = 0.0; TestPoint(0); } } sprintf(buf,"%g %g",frac1, frac2); } /* ** Clear the cache of GCs */ static void ClearGcCache(HtmlWidget *htmlPtr){ int i; for(i=0; iaGcCache[i].index ){ Tk_FreeGC(htmlPtr->display, htmlPtr->aGcCache[i].gc); htmlPtr->aGcCache[i].index = 0; TestPoint(0); }else{ TestPoint(0); } } } /* ** This routine is called when the widget command is deleted. If the ** widget isn't already in the process of being destroyed, this command ** starts that process rolling. ** ** This routine can be called in two ways. ** ** (1) The window is destroyed, which causes the command to be deleted. ** In this case, we don't have to do anything. ** ** (2) The command only is deleted (ex: "rename .html {}"). In that ** case we need to destroy the window. */ static void HtmlCmdDeletedProc(ClientData clientData){ HtmlWidget *htmlPtr = (HtmlWidget*) clientData; if (htmlPtr != NULL && htmlPtr->tkwin!=NULL ) { Tk_Window tkwin = htmlPtr->tkwin; htmlPtr->tkwin = NULL; Tk_DestroyWindow(tkwin); } } /* ** Reset the main layout context in the main widget. This happens ** before we redo the layout, or just before deleting the widget. */ static void ResetLayoutContext(HtmlWidget *htmlPtr){ htmlPtr->layoutContext.headRoom = 0; htmlPtr->layoutContext.top = 0; htmlPtr->layoutContext.bottom = 0; HtmlClearMarginStack(&htmlPtr->layoutContext.leftMargin); HtmlClearMarginStack(&htmlPtr->layoutContext.rightMargin); } /* ** This routine is invoked in order to redraw all or part of the HTML ** widget. This might happen because the display has changed, or in ** response to an expose event. In all cases, though, this routine ** is called by an idle callback. */ static void HtmlRedrawCallback(ClientData clientData){ HtmlWidget *htmlPtr = (HtmlWidget*)clientData; Tk_Window tkwin = htmlPtr->tkwin; Tk_Window clipwin = htmlPtr->clipwin; Pixmap pixmap; /* The buffer on which to render HTML */ int x, y, w, h; /* Virtual canvas coordinates of area to draw */ int hw; /* highlight thickness */ int insetX, insetY; /* Total highlight thickness, border width and ** padx/y */ int clipwinH, clipwinW; /* Width and height of the clipping window */ HtmlBlock *pBlock; /* For looping over blocks to be drawn */ int redoSelection = 0; /* True to recompute the selection */ /* ** Don't bother doing anything if the widget is in the process of ** being destroyed. */ if( tkwin==0 ){ goto redrawExit; } /* ** Recompute the layout, if necessary or requested. ** ** Calling HtmlLayout() is tricky because HtmlLayout() may invoke one ** or more callbacks (thru the "-imagecommand" callback, for instance) ** and these callbacks could, in theory, do nasty things like delete ** or unmap this widget. So we have to take precautions: ** ** * Don't remove the REDRAW_PENDING flag until after HtmlLayout() ** has been called, to prevent a recursive call to HtmlRedrawCallback(). ** ** * Call HtmlLock() on the htmlPtr structure to prevent it from ** being deleted out from under us. ** */ if( (htmlPtr->flags & RESIZE_ELEMENTS)!=0 && (htmlPtr->flags & STYLER_RUNNING)==0 ){ HtmlImage *pImage; for(pImage=htmlPtr->imageList; pImage; pImage=pImage->pNext){ pImage->pList = 0; } htmlPtr->lastSized = 0; htmlPtr->flags &= ~RESIZE_ELEMENTS; htmlPtr->flags |= RELAYOUT; } /* We used to make a distinction between RELAYOUT and EXTEND_LAYOUT. ** RELAYOUT would be used when the widget was resized, but the ** less compute-intensive EXTEND_LAYOUT would be used when new ** text was appended. ** ** Unfortunately, EXTEND_LAYOUT has some problem that arise when ** tables are used. The quick fix is to make an EXTEND_LAYOUT do ** a complete RELAYOUT. Someday, we need to fix EXTEND_LAYOUT so ** that it works right... */ if( (htmlPtr->flags & (RELAYOUT|EXTEND_LAYOUT))!=0 && (htmlPtr->flags & STYLER_RUNNING)==0 ){ htmlPtr->nextPlaced = 0; htmlPtr->nInput = 0; htmlPtr->varId = 0; htmlPtr->maxX = 0; htmlPtr->maxY = 0; ResetLayoutContext(htmlPtr); htmlPtr->firstBlock = 0; htmlPtr->lastBlock = 0; redoSelection = 1; htmlPtr->flags &= ~RELAYOUT; htmlPtr->flags |= HSCROLL | VSCROLL | REDRAW_TEXT | EXTEND_LAYOUT; } if( (htmlPtr->flags & EXTEND_LAYOUT) && htmlPtr->pFirst!=0 ){ HtmlLock(htmlPtr); HtmlLayout(htmlPtr); if( HtmlUnlock(htmlPtr) ) goto redrawExit; tkwin = htmlPtr->tkwin; htmlPtr->flags &= ~EXTEND_LAYOUT; HtmlFormBlocks(htmlPtr); HtmlMapControls(htmlPtr); if( redoSelection && htmlPtr->selBegin.p && htmlPtr->selEnd.p ){ HtmlUpdateSelection(htmlPtr,1); HtmlUpdateInsert(htmlPtr); } } htmlPtr->flags &= ~REDRAW_PENDING; /* No need to do any actual drawing if we aren't mapped */ if( !Tk_IsMapped(tkwin) ){ goto redrawExit; } /* Redraw the scrollbars. Take care here, since the scrollbar ** update command could (in theory) delete the html widget, or ** even the whole interpreter. Preserve critical data structures, ** and check to see if we are still alive before continuing. */ if( (htmlPtr->flags & (HSCROLL|VSCROLL)) != 0 ){ Tcl_Interp *interp = htmlPtr->interp; int result; char buf[200]; if( (htmlPtr->flags & HSCROLL)!=0 ){ if( htmlPtr->xScrollCmd && htmlPtr->xScrollCmd[0] ){ HtmlComputeHorizontalPosition(htmlPtr,buf); HtmlLock(htmlPtr); result = Tcl_VarEval(interp, htmlPtr->xScrollCmd, " ", buf, NULL); if( HtmlUnlock(htmlPtr) ) goto redrawExit; if (result != TCL_OK) { Tcl_AddErrorInfo(interp, "\n (horizontal scrolling command executed by html widget)"); Tcl_BackgroundError(interp); TestPoint(0); } } htmlPtr->flags &= ~HSCROLL; } if( (htmlPtr->flags & VSCROLL)!=0 && tkwin && Tk_IsMapped(tkwin) ){ if( htmlPtr->yScrollCmd && htmlPtr->yScrollCmd[0] ){ Tcl_Interp *interp = htmlPtr->interp; int result; char buf[200]; HtmlComputeVerticalPosition(htmlPtr,buf); HtmlLock(htmlPtr); result = Tcl_VarEval(interp, htmlPtr->yScrollCmd, " ", buf, NULL); if( HtmlUnlock(htmlPtr) ) goto redrawExit; if (result != TCL_OK) { Tcl_AddErrorInfo(interp, "\n (horizontal scrolling command executed by html widget)"); Tcl_BackgroundError(interp); TestPoint(0); } } htmlPtr->flags &= ~VSCROLL; } tkwin = htmlPtr->tkwin; if( tkwin==0 || !Tk_IsMapped(tkwin) ){ goto redrawExit; } if( htmlPtr->flags & REDRAW_PENDING ){ return; } clipwin = htmlPtr->clipwin; if( clipwin==0 ){ TestPoint(0); goto redrawExit; } } /* Redraw the focus highlight, if requested */ hw = htmlPtr->highlightWidth; if( htmlPtr->flags & REDRAW_FOCUS ){ if( hw>0 ){ GC gc; Tk_Window tkwin = htmlPtr->tkwin; if( htmlPtr->flags & GOT_FOCUS ){ gc = Tk_GCForColor(htmlPtr->highlightColorPtr, Tk_WindowId(tkwin)); TestPoint(0); }else{ gc = Tk_GCForColor(htmlPtr->highlightBgColorPtr, Tk_WindowId(tkwin)); TestPoint(0); } Tk_DrawFocusHighlight(tkwin, gc, hw, Tk_WindowId(tkwin)); } htmlPtr->flags &= ~REDRAW_FOCUS; } /* Draw the borders around the parameter of the window. This is ** drawn directly -- it is not double buffered. */ if( htmlPtr->flags & REDRAW_BORDER ){ htmlPtr->flags &= ~REDRAW_BORDER; Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), htmlPtr->border, hw, /* x */ hw, /* y */ Tk_Width(tkwin) - 2*hw, /* width */ Tk_Height(tkwin) - 2*hw, /* height */ htmlPtr->borderWidth, htmlPtr->relief); } /* ** If the styler is in a callback, unmap the clipping window and ** abort further processing. */ if( htmlPtr->flags & STYLER_RUNNING ){ if( Tk_IsMapped(clipwin) ){ Tk_UnmapWindow(clipwin); } goto earlyOut; } /* ** If we don't have a clipping window, then something is seriously ** wrong. We might as well give up. */ if( clipwin==NULL ){ TestPoint(0); goto earlyOut; } /* Resize, reposition and map the clipping window, if necessary */ insetX = htmlPtr->padx + htmlPtr->inset; insetY = htmlPtr->pady + htmlPtr->inset; if( htmlPtr->flags & RESIZE_CLIPWIN ){ int h, w; Tk_MoveResizeWindow(clipwin, insetX, insetY, htmlPtr->realWidth - 2*insetX, htmlPtr->realHeight - 2*insetY); if( !Tk_IsMapped(clipwin) ){ Tk_MapWindow(clipwin); } h = htmlPtr->realHeight - 2*insetY; if( htmlPtr->yOffset + h > htmlPtr->maxY ){ htmlPtr->yOffset = htmlPtr->maxY - h; } if( htmlPtr->yOffset < 0 ){ htmlPtr->yOffset = 0; } w = htmlPtr->realWidth - 2*insetX; if( htmlPtr->xOffset + h > htmlPtr->maxX ){ htmlPtr->xOffset = htmlPtr->maxX - w; } if( htmlPtr->xOffset < 0 ){ htmlPtr->xOffset = 0; } htmlPtr->flags &= ~RESIZE_CLIPWIN; } HtmlMapControls(htmlPtr); /* ** Compute the virtual canvas coordinates corresponding to the ** dirty region of the clipping window. */ clipwinW = Tk_Width(clipwin); clipwinH = Tk_Height(clipwin); if( htmlPtr->flags & REDRAW_TEXT ){ w = clipwinW; h = clipwinH; x = htmlPtr->xOffset; y = htmlPtr->yOffset; htmlPtr->dirtyLeft = 0; htmlPtr->dirtyTop = 0; htmlPtr->flags &= ~REDRAW_TEXT; }else{ if( htmlPtr->dirtyLeft < 0 ){ htmlPtr->dirtyLeft = 0; TestPoint(0); } if( htmlPtr->dirtyRight > clipwinW ){ htmlPtr->dirtyRight = clipwinW; TestPoint(0); } if( htmlPtr->dirtyTop < 0 ){ htmlPtr->dirtyTop = 0; TestPoint(0); } if( htmlPtr->dirtyBottom > clipwinH ){ htmlPtr->dirtyBottom = clipwinH; TestPoint(0); } w = htmlPtr->dirtyRight - htmlPtr->dirtyLeft; h = htmlPtr->dirtyBottom - htmlPtr->dirtyTop; x = htmlPtr->xOffset + htmlPtr->dirtyLeft; y = htmlPtr->yOffset + htmlPtr->dirtyTop; } /* Skip the rest of the drawing process if the area to be refreshed is ** less than zero */ if( w>0 && h>0 ){ Display *display = htmlPtr->display; int dead; GC gcBg; XRectangle xrec; /* printf("Redraw %dx%d at %d,%d\n", w, h, x, y); */ /* Allocate and clear a pixmap upon which to draw */ gcBg = HtmlGetGC(htmlPtr, COLOR_Background, FONT_Any); pixmap = Tk_GetPixmap(display, Tk_WindowId(clipwin),w,h,Tk_Depth(clipwin)); xrec.x = 0; xrec.y = 0; xrec.width = w; xrec.height = h; XFillRectangles(display, pixmap, gcBg, &xrec, 1); /* Render all visible HTML onto the pixmap */ HtmlLock(htmlPtr); for(pBlock=htmlPtr->firstBlock; pBlock; pBlock=pBlock->pNext){ if( pBlock->top <= y+h && pBlock->bottom >= y && pBlock->left <= x+w && pBlock->right >= x ){ HtmlBlockDraw(htmlPtr,pBlock,pixmap,x,y,w,h); if( htmlPtr->tkwin==0 ) break; } } dead = HtmlUnlock(htmlPtr); /* Finally, copy the pixmap onto the window and delete the pixmap */ if( !dead ){ XCopyArea(display, pixmap, Tk_WindowId(clipwin), gcBg, 0, 0, w, h, htmlPtr->dirtyLeft, htmlPtr->dirtyTop); } Tk_FreePixmap(display, pixmap); if( dead ) goto redrawExit; /* XFlush(display); */ } /* Redraw images, if requested */ if( htmlPtr->flags & REDRAW_IMAGES ){ HtmlImage *pImage; HtmlElement *pElem; int top, bottom, left, right; /* Coordinates of the clipping window */ int imageTop; /* Top edge of image */ top = htmlPtr->yOffset; bottom = top + HtmlUsableHeight(htmlPtr); left = htmlPtr->xOffset; right = left + HtmlUsableWidth(htmlPtr); for(pImage = htmlPtr->imageList; pImage; pImage=pImage->pNext){ for(pElem = pImage->pList; pElem; pElem=pElem->image.pNext){ if( pElem->image.redrawNeeded==0 ) continue; imageTop = pElem->image.y - pElem->image.ascent; if( imageTop > bottom || imageTop + pElem->image.h < top || pElem->image.x > right || pElem->image.x + pElem->image.w < left ){ TestPoint(0); continue; } HtmlDrawImage(pElem, Tk_WindowId(htmlPtr->clipwin), left, top, right, bottom); } } htmlPtr->flags &= ~REDRAW_IMAGES; } /* Set the dirty region to the empty set. */ earlyOut: htmlPtr->dirtyTop = LARGE_NUMBER; htmlPtr->dirtyLeft = LARGE_NUMBER; htmlPtr->dirtyBottom = 0; htmlPtr->dirtyRight = 0; redrawExit: return; } /* ** Make sure that a call to the HtmlRedrawCallback() routine has been ** queued. */ void HtmlScheduleRedraw(HtmlWidget *htmlPtr){ if( (htmlPtr->flags & REDRAW_PENDING)==0 && htmlPtr->tkwin!=0 && Tk_IsMapped(htmlPtr->tkwin) ){ Tcl_DoWhenIdle(HtmlRedrawCallback, (ClientData)htmlPtr); htmlPtr->flags |= REDRAW_PENDING; } } /* ** If any part of the screen needs to be redrawn, Then call this routine ** with the values of a box (in window coordinates) that needs to be ** redrawn. This routine will make sure an idle callback is scheduled ** to do the redraw. ** ** The box coordinates are relative to the clipping window (clipwin), ** not the main window (tkwin). */ void HtmlRedrawArea( HtmlWidget *htmlPtr, /* The widget to be redrawn */ int left, int top, /* Top left corner of area to redraw */ int right, int bottom /* bottom right corner of area to redraw */ ){ if( bottom < 0 ){ TestPoint(0); return; } if( top > htmlPtr->realHeight ){ TestPoint(0); return; } if( right < 0 ){ TestPoint(0); return; } if( left > htmlPtr->realWidth ){ TestPoint(0); return; } if( htmlPtr->dirtyTop > top ){ htmlPtr->dirtyTop = top; TestPoint(0);} if( htmlPtr->dirtyLeft > left ){ htmlPtr->dirtyLeft = left; TestPoint(0);} if( htmlPtr->dirtyBottom < bottom ){ htmlPtr->dirtyBottom = bottom; TestPoint(0); } if( htmlPtr->dirtyRight < right ){ htmlPtr->dirtyRight = right; TestPoint(0);} HtmlScheduleRedraw(htmlPtr); TestPoint(0); } /* Redraw the HtmlBlock given. */ void HtmlRedrawBlock(HtmlWidget *htmlPtr, HtmlBlock *p){ if( p ){ HtmlRedrawArea(htmlPtr, p->left - htmlPtr->xOffset, p->top - htmlPtr->yOffset, p->right - htmlPtr->xOffset + 1, p->bottom - htmlPtr->yOffset ); TestPoint(0); }else{ TestPoint(0); } } /* ** Call this routine to force the entire widget to be redrawn. */ void HtmlRedrawEverything(HtmlWidget *htmlPtr){ htmlPtr->flags |= REDRAW_FOCUS | REDRAW_TEXT | REDRAW_BORDER; HtmlScheduleRedraw(htmlPtr); TestPoint(0); } /* ** Do the redrawing right now. Don't wait. */ #if 0 /* NOT_USED */ static void HtmlRedrawPush(HtmlWidget *htmlPtr){ if( htmlPtr->flags & REDRAW_PENDING ){ Tcl_CancelIdleCall(HtmlRedrawCallback, (ClientData)htmlPtr); TestPoint(0); }else{ TestPoint(0); } HtmlRedrawCallback( (ClientData)htmlPtr ); } #endif /* ** Call this routine to cause all of the rendered HTML at the ** virtual canvas coordinate of Y and beyond to be redrawn. */ void HtmlRedrawText(HtmlWidget *htmlPtr, int y){ int yOffset; /* Top-most visible canvas coordinate */ int clipHeight; /* Height of the clipping window */ yOffset = htmlPtr->yOffset; clipHeight = HtmlUsableHeight(htmlPtr); y -= yOffset; if( y < clipHeight ){ HtmlRedrawArea(htmlPtr, 0, y, LARGE_NUMBER, clipHeight); TestPoint(0); }else{ TestPoint(0); } } /* ** Recalculate the preferred size of the html widget and pass this ** along to the geometry manager. */ static void HtmlRecomputeGeometry(HtmlWidget *htmlPtr){ int w, h; /* Total width and height of the widget */ htmlPtr->inset = htmlPtr->highlightWidth + htmlPtr->borderWidth; w = htmlPtr->width + 2*(htmlPtr->padx + htmlPtr->inset); h = htmlPtr->height + 2*(htmlPtr->pady + htmlPtr->inset); Tk_GeometryRequest(htmlPtr->tkwin, w, h); Tk_SetInternalBorder(htmlPtr->tkwin, htmlPtr->inset); TestPoint(0); } /* ** This routine is called in order to process a "configure" subcommand ** on the given html widget. */ int ConfigureHtmlWidget( Tcl_Interp *interp, /* Write error message to this interpreter */ HtmlWidget *htmlPtr, /* The Html widget to be configured */ int argc, /* Number of configuration arguments */ const char **argv, /* Text of configuration arguments */ int flags, /* Configuration flags */ int realign /* Always do a redraw if set */ ){ int rc; int i; int redraw = realign; /* True if a redraw is required. */ /* Scan thru the configuration options to see if we need to redraw ** the widget. */ for(i=0; redraw==0 && i4 && strncmp(argv[i],"-cursor",n)==0 ){ /* do nothing */ }else /* The default case */ { redraw = 1; } } rc = Tk_ConfigureWidget(interp, htmlPtr->tkwin, configSpecs, argc, (const char**)argv, (char *) htmlPtr, flags); if( rc!=TCL_OK || redraw==0 ){ TestPoint(0); return rc; } memset(htmlPtr->fontValid, 0, sizeof(htmlPtr->fontValid)); htmlPtr->apColor[COLOR_Normal] = htmlPtr->fgColor; htmlPtr->apColor[COLOR_Visited] = htmlPtr->oldLinkColor; htmlPtr->apColor[COLOR_Unvisited] = htmlPtr->newLinkColor; htmlPtr->apColor[COLOR_Selection] = htmlPtr->selectionColor; htmlPtr->apColor[COLOR_Background] = Tk_3DBorderColor(htmlPtr->border); Tk_SetBackgroundFromBorder(htmlPtr->tkwin, htmlPtr->border); if( htmlPtr->highlightWidth < 0 ){ htmlPtr->highlightWidth = 0; TestPoint(0);} if (htmlPtr->padx < 0) { htmlPtr->padx = 0; TestPoint(0);} if (htmlPtr->pady < 0) { htmlPtr->pady = 0; TestPoint(0);} if (htmlPtr->width < 100) { htmlPtr->width = 100; TestPoint(0);} if (htmlPtr->height < 100) { htmlPtr->height = 100; TestPoint(0);} if (htmlPtr->borderWidth < 0) {htmlPtr->borderWidth = 0; TestPoint(0);} htmlPtr->flags |= RESIZE_ELEMENTS | RELAYOUT | REDRAW_BORDER | RESIZE_CLIPWIN; HtmlRecomputeGeometry(htmlPtr); HtmlRedrawEverything(htmlPtr); ClearGcCache(htmlPtr); return rc; } /* ** Delete a single HtmlElement */ void HtmlDeleteElement(HtmlElement *p){ switch( p->base.type ){ case Html_Block: if( p->block.z ){ HtmlFree(p->block.z); } break; default: break; } HtmlFree(p); } /* ** Erase all data from the HTML widget. Bring it back to an ** empty screen. ** ** This happens (for example) when the "clear" method is invoked ** on the widget, or just before the widget is deleted. */ void HtmlClear(HtmlWidget *htmlPtr){ int i; HtmlElement *p, *pNext; HtmlDeleteControls(htmlPtr); for(p=htmlPtr->pFirst; p; p=pNext){ pNext = p->pNext; HtmlDeleteElement(p); } htmlPtr->pFirst = 0; htmlPtr->pLast = 0; htmlPtr->nToken = 0; if( htmlPtr->zText ){ HtmlFree(htmlPtr->zText); } htmlPtr->zText = 0; htmlPtr->nText = 0; htmlPtr->nAlloc = 0; htmlPtr->nComplete = 0; htmlPtr->iPlaintext = 0; for(i=N_PREDEFINED_COLOR; iapColor[i] != 0 ){ Tk_FreeColor(htmlPtr->apColor[i]); htmlPtr->apColor[i] = 0; } } for(i=0; iiDark[i] = 0; htmlPtr->iLight[i] = 0; } htmlPtr->colorUsed = 0; while( htmlPtr->imageList ){ HtmlImage *p = htmlPtr->imageList; htmlPtr->imageList = p->pNext; Tk_FreeImage(p->image); HtmlFree(p); TestPoint(0); } while( htmlPtr->styleStack ){ HtmlStyleStack *p = htmlPtr->styleStack; htmlPtr->styleStack = p->pNext; HtmlFree(p); } ClearGcCache(htmlPtr); ResetLayoutContext(htmlPtr); if( htmlPtr->zBaseHref ){ HtmlFree(htmlPtr->zBaseHref); htmlPtr->zBaseHref = 0; } htmlPtr->lastSized = 0; htmlPtr->nextPlaced = 0; htmlPtr->firstBlock = 0; htmlPtr->lastBlock = 0; htmlPtr->nInput = 0; htmlPtr->nForm = 0; htmlPtr->varId = 0; htmlPtr->paraAlignment = ALIGN_None; htmlPtr->rowAlignment = ALIGN_None; htmlPtr->anchorFlags = 0; htmlPtr->inDt = 0; htmlPtr->anchorStart = 0; htmlPtr->formStart = 0; htmlPtr->innerList = 0; htmlPtr->maxX = 0; htmlPtr->maxY = 0; htmlPtr->xOffset = 0; htmlPtr->yOffset = 0; htmlPtr->pInsBlock = 0; htmlPtr->ins.p = 0; htmlPtr->selBegin.p = 0; htmlPtr->selEnd.p = 0; htmlPtr->pSelStartBlock = 0; htmlPtr->pSelEndBlock = 0; } /* ** This routine attempts to delete the widget structure. But it won't ** do it if the widget structure is locked. If the widget structure is ** locked, then when HtmlUnlock() is called and the lock count reaches ** zero, this routine will be called to finish the job. */ static void DestroyHtmlWidget(HtmlWidget *htmlPtr){ int i; if( htmlPtr->locked>0 ) return; Tcl_DeleteCommand(htmlPtr->interp, htmlPtr->zCmdName); Tcl_DeleteCommand(htmlPtr->interp, htmlPtr->zClipwin); HtmlClear(htmlPtr); Tk_FreeOptions(configSpecs, (char*) htmlPtr, htmlPtr->display, 0); for(i=0; iaFont[i] != 0 ){ Tk_FreeFont(htmlPtr->aFont[i]); htmlPtr->aFont[i] = 0; } } for(i=0; izHandler[i] ){ HtmlFree(htmlPtr->zHandler[i]); htmlPtr->zHandler[i] = 0; } } if( htmlPtr->insTimer ){ Tcl_DeleteTimerHandler(htmlPtr->insTimer); htmlPtr->insTimer = 0; } HtmlFree(htmlPtr->zClipwin); HtmlFree(htmlPtr); } /* ** Remove a lock from the HTML widget. If the widget has been ** deleted, then delete the widget structure. Return 1 if the ** widget has been deleted. Return 0 if it still exists. ** ** Normal Tk code (that is to say, code in the Tk core) uses ** Tcl_Preserve() and Tcl_Release() to accomplish what this ** function does. But preserving and releasing are much more ** common in this code than in regular widgets, so this routine ** was invented to do the same thing easier and faster. */ int HtmlUnlock(HtmlWidget *htmlPtr){ htmlPtr->locked--; if( htmlPtr->tkwin==0 && htmlPtr->locked<=0 ){ Tcl_Interp *interp = htmlPtr->interp; Tcl_Preserve(interp); DestroyHtmlWidget(htmlPtr); Tcl_Release(interp); return 1; } return htmlPtr->tkwin==0; } /* ** Lock the HTML widget. This prevents the widget structure from ** being deleted even if the widget itself is destroyed. There must ** be a call to HtmlUnlock() to release the structure. */ void HtmlLock(HtmlWidget *htmlPtr){ htmlPtr->locked++; } /* ** This routine checks to see if an HTML widget has been ** destroyed. It is always called after calling HtmlLock(). ** ** If the widget has been destroyed, then the structure ** is unlocked and the function returns 1. If the widget ** has not been destroyed, then the structure is not unlocked ** and the routine returns 0. ** ** This routine is intended for use in code like the following: ** ** HtmlLock(htmlPtr); ** // Do something that might destroy the widget ** if( HtmlIsDead(htmlPtr) ) return; ** // Do something that might destroy the widget ** if( HtmlIsDead(htmlPtr) ) return; ** // Do something that might destroy the widget ** if( HtmlUnlock(htmlPtr) ) return; */ int HtmlIsDead(HtmlWidget *htmlPtr){ if( htmlPtr->tkwin==0 ){ HtmlUnlock(htmlPtr); return 1; } return 0; } /* ** Flash the insertion cursor. */ void HtmlFlashCursor(ClientData clientData){ HtmlWidget *htmlPtr = (HtmlWidget*)clientData; if( htmlPtr->pInsBlock==0 || htmlPtr->insOnTime<=0 || htmlPtr->insOffTime<=0 ){ htmlPtr->insTimer = 0; TestPoint(0); return; } HtmlRedrawBlock(htmlPtr, htmlPtr->pInsBlock); if( (htmlPtr->flags & GOT_FOCUS)==0 ){ htmlPtr->insStatus = 0; htmlPtr->insTimer = 0; TestPoint(0); }else if( htmlPtr->insStatus ){ htmlPtr->insTimer = Tcl_CreateTimerHandler(htmlPtr->insOffTime, HtmlFlashCursor, clientData); htmlPtr->insStatus = 0; TestPoint(0); }else{ htmlPtr->insTimer = Tcl_CreateTimerHandler(htmlPtr->insOnTime, HtmlFlashCursor, clientData); htmlPtr->insStatus = 1; TestPoint(0); } } /* ** Return a GC from the cache. As many as N_CACHE_GCs are kept valid ** at any one time. They are replaced using an LRU algorithm. ** ** A value of FONT_Any (-1) for the font means "don't care". */ GC HtmlGetGC(HtmlWidget *htmlPtr, int color, int font){ int i, j; GcCache *p = htmlPtr->aGcCache; XGCValues gcValues; int mask; Tk_Font tkfont; /* ** Check for an existing GC. */ if( color < 0 || color >= N_COLOR ){ color = 0; TestPoint(0); } if( font < FONT_Any || font >= N_FONT ){ font = FONT_Default; TestPoint(0); } for(i=0; iindex==0 ){ TestPoint(0); continue; } if( (font<0 || p->font==font) && p->color==color ){ if( p->index>1 ){ for(j=0; jaGcCache[j].index && htmlPtr->aGcCache[j].index < p->index ){ htmlPtr->aGcCache[j].index++; } } p->index = 1; } return htmlPtr->aGcCache[i].gc; } } /* ** No GC matches. Find a place to allocate a new GC. */ p = htmlPtr->aGcCache; for(i=0; iindex==0 || p->index==N_CACHE_GC ){ TestPoint(0); break; } } if( p->index ){ Tk_FreeGC(htmlPtr->display, p->gc); } gcValues.foreground = htmlPtr->apColor[color]->pixel; gcValues.graphics_exposures = True; mask = GCForeground | GCGraphicsExposures; if( font<0 ){ font = FONT_Default; TestPoint(0); } tkfont = HtmlGetFont(htmlPtr, font); if( tkfont ){ gcValues.font = Tk_FontId(tkfont); mask |= GCFont; } p->gc = Tk_GetGC(htmlPtr->tkwin, mask, &gcValues); if( p->index==0 ){ p->index = N_CACHE_GC + 1; TestPoint(0); } for(j=0; jaGcCache[j].index && htmlPtr->aGcCache[j].index < p->index ){ htmlPtr->aGcCache[j].index++; } } p->index = 1; p->font = font; p->color = color; return p->gc; } /* ** Retrieve any valid GC. The font and color don't matter since the ** GC will only be used for copying. */ GC HtmlGetAnyGC(HtmlWidget *htmlPtr){ int i; GcCache *p = htmlPtr->aGcCache; for(i=0; iindex ){ TestPoint(0); return p->gc; } } TestPoint(0); return HtmlGetGC(htmlPtr, COLOR_Normal, FONT_Default); } /* ** All window events (for both tkwin and clipwin) are ** sent to this routine. */ static void HtmlEventProc(ClientData clientData, XEvent *eventPtr){ HtmlWidget *htmlPtr = (HtmlWidget*) clientData; int redraw_needed = 0; XConfigureRequestEvent *p; switch( eventPtr->type ){ case GraphicsExpose: case Expose: if( htmlPtr->tkwin==0 ){ /* The widget is being deleted. Do nothing */ TestPoint(0); }else if( eventPtr->xexpose.window!=Tk_WindowId(htmlPtr->tkwin) ){ /* Exposure in the clipping window */ HtmlRedrawArea(htmlPtr, eventPtr->xexpose.x - 1, eventPtr->xexpose.y - 1, eventPtr->xexpose.x + eventPtr->xexpose.width + 1, eventPtr->xexpose.y + eventPtr->xexpose.height + 1); TestPoint(0); }else{ /* Exposure in the main window */ htmlPtr->flags |= REDRAW_BORDER; HtmlScheduleRedraw(htmlPtr); TestPoint(0); } break; case DestroyNotify: if( (htmlPtr->flags & REDRAW_PENDING) ){ Tcl_CancelIdleCall(HtmlRedrawCallback, (ClientData)htmlPtr); htmlPtr->flags &= ~REDRAW_PENDING; } if( htmlPtr->tkwin != 0 ){ if( eventPtr->xany.window!=Tk_WindowId(htmlPtr->tkwin) ){ Tk_DestroyWindow(htmlPtr->tkwin); htmlPtr->clipwin = 0; break; } htmlPtr->tkwin = 0; Tcl_DeleteCommand(htmlPtr->interp, htmlPtr->zCmdName); Tcl_DeleteCommand(htmlPtr->interp, htmlPtr->zClipwin); } HtmlUnlock(htmlPtr); break; case ConfigureNotify: if( htmlPtr->tkwin!=0 && eventPtr->xconfigure.window==Tk_WindowId(htmlPtr->tkwin) ){ p = (XConfigureRequestEvent*)eventPtr; if( p->width != htmlPtr->realWidth ){ redraw_needed = 1; htmlPtr->realWidth = p->width; TestPoint(0); }else{ TestPoint(0); } if( p->height != htmlPtr->realHeight ){ redraw_needed = 1; htmlPtr->realHeight = p->height; TestPoint(0); }else{ TestPoint(0); } if( redraw_needed ){ htmlPtr->flags |= RELAYOUT | VSCROLL | HSCROLL | RESIZE_CLIPWIN; HtmlRedrawEverything(htmlPtr); TestPoint(0); }else{ TestPoint(0); } } break; case FocusIn: if( htmlPtr->tkwin!=0 && eventPtr->xfocus.window==Tk_WindowId(htmlPtr->tkwin) && eventPtr->xfocus.detail != NotifyInferior ){ htmlPtr->flags |= GOT_FOCUS | REDRAW_FOCUS; HtmlScheduleRedraw(htmlPtr); HtmlUpdateInsert(htmlPtr); TestPoint(0); }else{ TestPoint(0); } break; case FocusOut: if( htmlPtr->tkwin!=0 && eventPtr->xfocus.window==Tk_WindowId(htmlPtr->tkwin) && eventPtr->xfocus.detail != NotifyInferior ){ htmlPtr->flags &= ~GOT_FOCUS; htmlPtr->flags |= REDRAW_FOCUS; HtmlScheduleRedraw(htmlPtr); TestPoint(0); }else{ TestPoint(0); } break; } } /* ** The rendering and layout routines should call this routine in order to get ** a font structure. The iFont parameter specifies which of the N_FONT ** fonts should be obtained. The font is allocated if necessary. ** ** Because the -fontcommand callback can be invoked, this function can ** (in theory) cause the HTML widget to be changed arbitrarily or even ** deleted. Callers of this function much be prepared to be called ** recursively and/or to have the HTML widget deleted out from under ** them. This routine will return NULL if the HTML widget is deleted. */ Tk_Font HtmlGetFont( HtmlWidget *htmlPtr, /* The HTML widget to which the font applies */ int iFont /* Which font to obtain */ ){ Tk_Font toFree = 0; if( iFont<0 ){ iFont = 0; TestPoint(0); } if( iFont>=N_FONT ){ iFont = N_FONT - 1; CANT_HAPPEN; } /* ** If the font has previously been allocated, but the "fontValid" bitmap ** shows it is no longer valid, then mark it for freeing later. We use ** a policy of allocate-before-free because Tk's font cache operates ** much more efficiently that way. */ if( !FontIsValid(htmlPtr, iFont) && htmlPtr->aFont[iFont]!=0 ){ toFree = htmlPtr->aFont[iFont]; htmlPtr->aFont[iFont] = 0; TestPoint(0); } /* ** If we need to allocate a font, first construct the font name then ** allocate it. */ if( htmlPtr->aFont[iFont]==0 ){ char name[200]; /* Name of the font */ name[0] = 0; /* Run the -fontcommand if it is specified */ if( htmlPtr->zFontCommand && htmlPtr->zFontCommand[0] ){ int iFam; /* The font family index. Value between 0 and 7 */ Tcl_DString str; /* The command we'll execute to get the font name */ char *zSep = ""; /* Separator between font attributes */ int rc; /* Return code from the font command */ char zBuf[100]; /* Temporary buffer */ Tcl_DStringInit(&str); Tcl_DStringAppend(&str, htmlPtr->zFontCommand, -1); sprintf(zBuf, " %d {", FontSize(iFont)+1); Tcl_DStringAppend(&str,zBuf, -1); iFam = iFont / N_FONT_SIZE ; if( iFam & 1 ){ Tcl_DStringAppend(&str,"bold",-1); zSep = " "; } if( iFam & 2 ){ Tcl_DStringAppend(&str,zSep,-1); Tcl_DStringAppend(&str,"italic",-1); zSep = " "; } if( iFam & 4 ){ Tcl_DStringAppend(&str,zSep,-1); Tcl_DStringAppend(&str,"fixed",-1); } Tcl_DStringAppend(&str,"}",-1); HtmlLock(htmlPtr); rc = Tcl_GlobalEval(htmlPtr->interp, Tcl_DStringValue(&str)); Tcl_DStringFree(&str); if( HtmlUnlock(htmlPtr) ){ return NULL; } if( rc!=TCL_OK ){ Tcl_AddErrorInfo(htmlPtr->interp, "\n (-fontcommand callback of HTML widget)"); Tcl_BackgroundError(htmlPtr->interp); }else{ sprintf(name,"%.100s", Tcl_GetStringResult(htmlPtr->interp)); } Tcl_ResetResult(htmlPtr->interp); } /* ** If the -fontcommand failed or returned an empty string, or if ** there is no -fontcommand, then get the default font name. */ if( name[0]==0 ){ char *familyStr = ""; int iFamily; int iSize; int size; iFamily = iFont / N_FONT_SIZE; iSize = iFont % N_FONT_SIZE + 1; switch( iFamily ){ case 0: familyStr = "helvetica -%d"; break; case 1: familyStr = "helvetica -%d bold"; break; case 2: familyStr = "helvetica -%d italic"; break; case 3: familyStr = "helvetica -%d bold italic"; break; case 4: familyStr = "courier -%d"; break; case 5: familyStr = "courier -%d bold"; break; case 6: familyStr = "courier -%d italic"; break; case 7: familyStr = "courier -%d bold italic"; break; default: familyStr = "helvetica -14"; CANT_HAPPEN; } switch( iSize ){ case 1: size = 8; break; case 2: size = 10; break; case 3: size = 12; break; case 4: size = 14; break; case 5: size = 16; break; case 6: size = 18; break; case 7: size = 24; break; default: size = 14; CANT_HAPPEN; } sprintf(name, familyStr, size); } /* Get the named font */ htmlPtr->aFont[iFont] = Tk_GetFont(htmlPtr->interp, htmlPtr->tkwin, name); if( htmlPtr->aFont[iFont]==0 ){ Tcl_AddErrorInfo(htmlPtr->interp, "\n (trying to create a font named \""); Tcl_AddErrorInfo(htmlPtr->interp, name); Tcl_AddErrorInfo(htmlPtr->interp, "\" in the HTML widget)"); Tcl_BackgroundError(htmlPtr->interp); htmlPtr->aFont[iFont] = Tk_GetFont(htmlPtr->interp, htmlPtr->tkwin, "fixed"); } if( htmlPtr->aFont[iFont]==0 ){ Tcl_AddErrorInfo(htmlPtr->interp, "\n (trying to create font \"fixed\" in the HTML widget)"); Tcl_BackgroundError(htmlPtr->interp); htmlPtr->aFont[iFont] = Tk_GetFont(htmlPtr->interp, htmlPtr->tkwin, "helvetica -12"); } FontSetValid(htmlPtr, iFont); TestPoint(0); } /* ** Free the expired font, if any. */ if( toFree!=0 ){ Tk_FreeFont(toFree); } return htmlPtr->aFont[iFont]; } /* ** Compute the squared distance between two colors */ static float colorDistance(XColor *pA, XColor *pB){ float x, y, z; x = 0.30 * (pA->red - pB->red); y = 0.61 * (pA->green - pB->green); z = 0.11 * (pA->blue - pB->blue); TestPoint(0); return x*x + y*y + z*z; } /* ** This routine returns an index between 0 and N_COLOR-1 which indicates ** which XColor structure in the apColor[] array of htmlPtr should be ** used to describe the color specified by the given name. */ int HtmlGetColorByName(HtmlWidget *htmlPtr, char *zColor){ XColor *pNew; int iColor; Tk_Uid name; int i, n; char zAltColor[16]; /* Netscape accepts color names that are just HEX values, without ** the # up front. This isn't valid HTML, but we support it for ** compatibility. */ n = strlen(zColor); /* trucate any spaces on the end */ while (n>0 && zColor[n-1]==' ') { zColor[n-1] = '\0'; n--; } if( n==6 || n==3 || n==9 || n==12 ){ for(i=0; iinterp, htmlPtr->clipwin, name); if( pNew==0 ){ return 0; /* Color 0 is always the default */ } iColor = GetColorByValue(htmlPtr, pNew); Tk_FreeColor(pNew); return iColor; } /* ** Macros used in the computation of appropriate shadow colors. */ #define MAX_COLOR 65535 #define MAX(A,B) ((A)<(B)?(B):(A)) #define MIN(A,B) ((A)<(B)?(A):(B)) /* ** Check to see if the given color is too dark to be easily distinguished ** from black. */ static int isDarkColor(XColor *p){ float x, y, z; x = 0.50 * p->red; y = 1.00 * p->green; z = 0.28 * p->blue; return (x*x + y*y + z*z)<0.05*MAX_COLOR*MAX_COLOR; } /* ** Given that the background color is iBgColor, figure out an ** appropriate color for the dark part of a 3D shadow. */ int HtmlGetDarkShadowColor(HtmlWidget *htmlPtr, int iBgColor){ if( htmlPtr->iDark[iBgColor]==0 ){ XColor *pRef, val; pRef = htmlPtr->apColor[iBgColor]; if( isDarkColor(pRef) ){ int t1, t2; t1 = MIN(MAX_COLOR,pRef->red*1.2); t2 = (pRef->red*3 + MAX_COLOR)/4; val.red = MAX(t1,t2); t1 = MIN(MAX_COLOR,pRef->green*1.2); t2 = (pRef->green*3 + MAX_COLOR)/4; val.green = MAX(t1,t2); t1 = MIN(MAX_COLOR,pRef->blue*1.2); t2 = (pRef->blue*3 + MAX_COLOR)/4; val.blue = MAX(t1,t2); }else{ val.red = pRef->red*0.6; val.green = pRef->green*0.6; val.blue = pRef->blue*0.6; } htmlPtr->iDark[iBgColor] = GetColorByValue(htmlPtr, &val) + 1; } return htmlPtr->iDark[iBgColor] - 1; } /* ** Check to see if the given color is too light to be easily distinguished ** from white. */ static int isLightColor(XColor *p){ return p->green>=0.85*MAX_COLOR; } /* ** Given that the background color is iBgColor, figure out an ** appropriate color for the bright part of the 3D shadow. */ int HtmlGetLightShadowColor(HtmlWidget *htmlPtr, int iBgColor){ if( htmlPtr->iLight[iBgColor]==0 ){ XColor *pRef, val; pRef = htmlPtr->apColor[iBgColor]; if( isLightColor(pRef) ){ val.red = pRef->red*0.9; val.green = pRef->green*0.9; val.blue = pRef->blue*0.9; }else{ int t1, t2; t1 = MIN(MAX_COLOR,pRef->green*1.4); t2 = (pRef->green + MAX_COLOR)/2; val.green = MAX(t1,t2); t1 = MIN(MAX_COLOR,pRef->red*1.4); t2 = (pRef->red + MAX_COLOR)/2; val.red = MAX(t1,t2); t1 = MIN(MAX_COLOR,pRef->blue*1.4); t2 = (pRef->blue + MAX_COLOR)/2; val.blue = MAX(t1,t2); } htmlPtr->iLight[iBgColor] = GetColorByValue(htmlPtr, &val) + 1; } return htmlPtr->iLight[iBgColor] - 1; } /* ** Find a color integer for the color whose color components ** are given by pRef. */ LOCAL int GetColorByValue(HtmlWidget *htmlPtr, XColor *pRef){ int i; float dist; float closestDist; int closest; /* int r, g, b; # define COLOR_MASK 0xf800 */ XColor* q; q = Tk_GetColorByValue(htmlPtr->clipwin, pRef); /* Search for an exact match */ /* r = pRef->red &= COLOR_MASK; g = pRef->green &= COLOR_MASK; b = pRef->blue &= COLOR_MASK; */ for(i=0; iapColor[i]; /* if( p && (p->red & COLOR_MASK)==r && (p->green & COLOR_MASK)==g && (p->blue & COLOR_MASK)==b ){ */ if (p && (q->red == p->red) && (q->green == p->green) && (q->blue == p->blue)) { htmlPtr->colorUsed |= (1<apColor[i]==0 ){ htmlPtr->apColor[i] = Tk_GetColorByValue(htmlPtr->clipwin, pRef); htmlPtr->colorUsed |= (1<colorUsed >> i) & 1) == 0 ){ Tk_FreeColor(htmlPtr->apColor[i]); htmlPtr->apColor[i] = Tk_GetColorByValue(htmlPtr->clipwin, pRef); htmlPtr->colorUsed |= (1<apColor[0]); for(i=1; iapColor[i]); if( dist < closestDist ){ closestDist = dist; closest = i; } } return i; } /* ** This routine searchs for a hyperlink beneath the coordinates x,y ** and returns a pointer to the HREF for that hyperlink. The text ** is held one of the markup.argv[] fields of the markup. */ char *HtmlGetHref(HtmlWidget *htmlPtr, int x, int y){ HtmlBlock *pBlock; HtmlElement *pElem; for(pBlock=htmlPtr->firstBlock; pBlock; pBlock=pBlock->pNext){ if( pBlock->top > y || pBlock->bottom < y || pBlock->left > x || pBlock->right < x ){ TestPoint(0); continue; } pElem = pBlock->base.pNext; if( (pElem->base.style.flags & STY_Anchor)==0 ){ TestPoint(0); continue; } switch( pElem->base.type ){ case Html_Text: case Html_Space: case Html_IMG: while( pElem && pElem->base.type!=Html_A ){ pElem = pElem->base.pPrev; } if( pElem==0 || pElem->base.type!=Html_A ){ break; } return HtmlMarkupArg(pElem,"href", 0); default: break; } } TestPoint(0); return 0; } /* ** Change the "yOffset" field from its current value to the value given. ** This has the effect of scrolling the widget vertically. */ void HtmlVerticalScroll(HtmlWidget *htmlPtr, int yOffset){ int inset; /* The 3D border plus the pady */ int h; /* Height of the clipping window */ int diff; /* Difference between old and new offset */ GC gc; /* Graphics context used for copying */ int w; /* Width of text area */ if( yOffset==htmlPtr->yOffset ){ TestPoint(0); return; } inset = htmlPtr->pady + htmlPtr->inset; h = htmlPtr->realHeight - 2*inset; if( (htmlPtr->flags & REDRAW_TEXT)!=0 || (htmlPtr->dirtyTop < h && htmlPtr->dirtyBottom > 0) || htmlPtr->yOffset > yOffset + (h - 30) || htmlPtr->yOffset < yOffset - (h - 30) ){ htmlPtr->yOffset = yOffset; htmlPtr->flags |= VSCROLL | REDRAW_TEXT; HtmlScheduleRedraw(htmlPtr); TestPoint(0); return; } diff = htmlPtr->yOffset - yOffset; gc = HtmlGetAnyGC(htmlPtr); w = htmlPtr->realWidth - 2*(htmlPtr->inset + htmlPtr->padx); htmlPtr->flags |= VSCROLL; htmlPtr->yOffset = yOffset; #ifndef MAC_OSX_TK if( diff < 0 ){ XCopyArea(htmlPtr->display, Tk_WindowId(htmlPtr->clipwin), /* source */ Tk_WindowId(htmlPtr->clipwin), /* destination */ gc, 0, -diff, /* source X, Y */ w, h + diff, /* Width and height */ 0, 0); /* Destination X, Y */ HtmlRedrawArea(htmlPtr, 0, h + diff, w, h); TestPoint(0); }else{ XCopyArea(htmlPtr->display, Tk_WindowId(htmlPtr->clipwin), /* source */ Tk_WindowId(htmlPtr->clipwin), /* destination */ gc, 0, 0, /* source X, Y */ w, h - diff, /* Width and height */ 0, diff); /* Destination X, Y */ HtmlRedrawArea(htmlPtr, 0, 0, w, diff); TestPoint(0); } #else HtmlRedrawArea(htmlPtr, 0, 0, w, h); #endif /* HtmlMapControls(htmlPtr);*/ } /* ** Change the "xOffset" field from its current value to the value given. ** This has the effect of scrolling the widget horizontally. */ void HtmlHorizontalScroll(HtmlWidget *htmlPtr, int xOffset){ if( xOffset==htmlPtr->xOffset ){ TestPoint(0); return; } htmlPtr->xOffset = xOffset; HtmlMapControls(htmlPtr); htmlPtr->flags |= HSCROLL | REDRAW_TEXT; HtmlScheduleRedraw(htmlPtr); TestPoint(0); } /* ** The following array defines all possible widget command. The main ** widget command function just parses up the command line, then vectors ** control to one of the command service routines defined in the ** following array: */ static struct HtmlSubcommand { char *zCmd1; /* First-level subcommand. Required */ char *zCmd2; /* Second-level subcommand. May be NULL */ int minArgc; /* Minimum number of arguments */ int maxArgc; /* Maximum number of arguments */ char *zHelp; /* Help string if wrong number of arguments */ int (*xFunc)(HtmlWidget*,Tcl_Interp*,int,const char**); /* Cmd service routine */ } aSubcommand[] = { { "cget", 0, 3, 3, "CONFIG-OPTION", HtmlCgetCmd }, { "clear", 0, 2, 2, 0, HtmlClearCmd }, { "configure", 0, 2, 0, "?ARGS...?", HtmlConfigCmd }, { "href", 0, 4, 4, "X Y", HtmlHrefCmd }, { "index", 0, 3, 3, "INDEX", HtmlIndexCmd }, { "insert", 0, 3, 3, "INDEX", HtmlInsertCmd }, { "names", 0, 2, 2, 0, HtmlNamesCmd }, { "parse", 0, 3, 3, "HTML-TEXT", HtmlParseCmd }, { "resolve", 0, 2, 0, "?URI ...?", HtmlResolveCmd }, { "selection", "clear", 3, 3, 0, HtmlSelectionClearCmd}, { 0, "set", 5, 5, "START END", HtmlSelectionSetCmd }, { "text", "ascii", 5, 5, "START END", HtmlTextAsciiCmd}, { 0, "delete", 5, 5, "START END", 0 }, { 0, "html", 5, 5, "START END", 0 }, { 0, "insert", 5, 5, "INDEX TEXT", 0 }, { "token", "append", 5, 5, "TAG ARGUMENTS", 0 }, { 0, "delete", 4, 5, "INDEX ?INDEX?", 0 }, { 0, "find", 4, 6, "TAG ?before|after INDEX?", 0 }, { 0, "get", 4, 5, "INDEX ?INDEX?", 0 }, { 0, "handler", 4, 5, "TAG ?SCRIPT?", HtmlTokenHandlerCmd }, { 0, "insert", 6, 6, "INDEX TAG ARGUMENTS", 0 }, { 0, "list", 5, 5, "START END", HtmlTokenListCmd }, { "xview", 0, 2, 5, "OPTIONS...", HtmlXviewCmd }, { "yview", 0, 2, 5, "OPTIONS...", HtmlYviewCmd }, #ifdef DEBUG { "debug", "dump", 5, 5, "START END", HtmlDebugDumpCmd }, { 0, "testpt", 4, 4, "FILENAME", HtmlDebugTestPtCmd }, #endif }; #define nSubcommand (sizeof(aSubcommand)/sizeof(aSubcommand[0])) /* ** This routine implements the command used by individual HTML widgets. */ static int HtmlWidgetCommand( ClientData clientData, /* The HTML widget data structure */ Tcl_Interp *interp, /* Current interpreter. */ int argc, /* Number of arguments. */ const char **argv /* Argument strings. */ ){ HtmlWidget *htmlPtr = (HtmlWidget*) clientData; size_t length; int c; int i; struct HtmlSubcommand *pCmd; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " option ?arg arg ...?\"", 0); TestPoint(0); return TCL_ERROR; } c = argv[1][0]; length = strlen(argv[1]); for(i=0, pCmd=aSubcommand; izCmd1==0 || c!=pCmd->zCmd1[0] || strncmp(pCmd->zCmd1,argv[1],length)!=0 ){ TestPoint(0); continue; } if( pCmd->zCmd2 ){ int length2; int j; if( argc<3 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " ", pCmd->zCmd1, " SUBCOMMAND ?OPTIONS...?", 0); TestPoint(0); return TCL_ERROR; } length2 = strlen(argv[2]); for(j=i; jzCmd1==0); j++, pCmd++){ if( strncmp(pCmd->zCmd2,argv[2],length2)==0 ){ TestPoint(0); break; } } if( j>=nSubcommand || (j!=i && aSubcommand[j].zCmd1!=0) ){ Tcl_AppendResult(interp,"unknown subcommand \"", argv[2], "\" -- should be one of:", 0); for(j=i; jminArgc || (argc>pCmd->maxArgc && pCmd->maxArgc>0) ){ Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0], " ", pCmd->zCmd1, 0); if( pCmd->zCmd2 ){ Tcl_AppendResult(interp, " ", pCmd->zCmd2, 0); TestPoint(0); } if( pCmd->zHelp ){ Tcl_AppendResult(interp, " ", pCmd->zHelp, 0); TestPoint(0); } Tcl_AppendResult(interp, "\"", 0); TestPoint(0); return TCL_ERROR; } if( pCmd->xFunc==0 ){ Tcl_AppendResult(interp,"command not yet implemented", 0); TestPoint(0); return TCL_ERROR; } TestPoint(0); return (*pCmd->xFunc)(htmlPtr, interp, argc, argv); } Tcl_AppendResult(interp,"unknown command \"", argv[1], "\" -- should be " "one of:", 0); for(i=0; itkwin = new; htmlPtr->clipwin = clipwin; htmlPtr->zClipwin = zClipwin; htmlPtr->display = Tk_Display(new); htmlPtr->interp = interp; htmlPtr->zCmdName = (char*)&htmlPtr[1]; strcpy(htmlPtr->zCmdName, argv[1]); htmlPtr->relief = TK_RELIEF_FLAT; htmlPtr->dirtyLeft = LARGE_NUMBER; htmlPtr->dirtyTop = LARGE_NUMBER; htmlPtr->flags = RESIZE_CLIPWIN; htmlPtr->varId = varId++; Tcl_CreateCommand(interp, htmlPtr->zCmdName, HtmlWidgetCommand, (ClientData)htmlPtr, HtmlCmdDeletedProc); Tcl_CreateCommand(interp, htmlPtr->zClipwin, HtmlWidgetCommand, (ClientData)htmlPtr, HtmlCmdDeletedProc); Tk_SetClass(new,"Html"); Tk_SetClass(clipwin,"HtmlClip"); Tk_CreateEventHandler(htmlPtr->tkwin, ExposureMask|StructureNotifyMask|FocusChangeMask, HtmlEventProc, (ClientData) htmlPtr); Tk_CreateEventHandler(htmlPtr->clipwin, ExposureMask|StructureNotifyMask, HtmlEventProc, (ClientData) htmlPtr); if (ConfigureHtmlWidget(interp, htmlPtr, argc-2, argv+2, 0, 1) != TCL_OK) { goto error; } Tcl_SetResult(interp,Tk_PathName(htmlPtr->tkwin),NULL); return TCL_OK; error: Tk_DestroyWindow(htmlPtr->tkwin); return TCL_ERROR; } /* html reformat $from $to $text ** ** Convert the format of text. */ if( c=='r' && strncmp(z,"reformat",n)==0 ){ if( argc!=5 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " reformat FROM TO TEXT", (char *) NULL); return TCL_ERROR; } Tcl_AppendResult(interp, "not yet implemented", 0); return TCL_ERROR; }else /* html urljoin $scheme $authority $path $query $fragment ** ** Merge together the parts of a URL into a single value URL. */ if( c=='u' && strncmp(z,"urljoin",n)==0 ){ if( argc!=7 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " url join SCHEME AUTHORITY PATH QUERY FRAGMENT\"", 0); return TCL_ERROR; } Tcl_AppendResult(interp, "not yet implemented", 0); return TCL_ERROR; }else /* html urlsplit $url ** ** Split a URL into a list of its parts. */ if( c=='u' && strncmp(z,"urlsplit",n)==0 ){ if( argc!=3 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " url split URL\"", 0); return TCL_ERROR; } Tcl_AppendResult(interp, "not yet implemented", 0); return TCL_ERROR; }else /* No match. Report an error. */ { Tcl_AppendResult(interp, "unknown command \"", z, "\": should be " "a window name or one of: " "reformat urljoin urlsplit", 0); return TCL_ERROR; } return TCL_OK; } /* ** The following mess is used to define DLL_EXPORT. DLL_EXPORT is ** blank except when we are building a Windows95/NT DLL from this ** library. Some special trickery is necessary to make this wall ** work together with makeheaders. */ #if INTERFACE #define DLL_EXPORT #endif #if defined(USE_TCL_STUBS) && defined(__WIN32__) # undef DLL_EXPORT # define DLL_EXPORT __declspec(dllexport) #endif /* ** This routine is used to register the "html" command with the ** Tcl interpreter. This is the only routine in this file with ** external linkage. */ DLL_EXPORT int Tkhtml1_Init(Tcl_Interp *interp) { if (Tcl_InitStubs(interp, TCL_PATCH_LEVEL, 0) == NULL) return TCL_ERROR; if (Tk_InitStubs(interp, TK_PATCH_LEVEL, 0) == NULL) return TCL_ERROR; Tcl_CreateCommand(interp,"html", HtmlCommand, Tk_MainWindow(interp), 0); #ifdef DEBUG Tcl_LinkVar(interp, "HtmlTraceMask", (char*)&HtmlTraceMask, TCL_LINK_INT); #endif if (Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION) != TCL_OK) return TCL_ERROR; return TCL_OK; }