diff options
Diffstat (limited to 'src/htmlwidget.c')
-rw-r--r-- | src/htmlwidget.c | 2043 |
1 files changed, 2043 insertions, 0 deletions
diff --git a/src/htmlwidget.c b/src/htmlwidget.c new file mode 100644 index 0000000..712ca3c --- /dev/null +++ b/src/htmlwidget.c @@ -0,0 +1,2043 @@ +/* +** 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 <tk.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include "htmlwidget.h" +/* +#ifdef USE_TK_STUBS +# include <tkIntXlibDecls.h> +#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; i<N_CACHE_GC; i++){ + if( htmlPtr->aGcCache[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, 0); + 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, 0); + 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 && i<argc; i+=2){ + int c; + int n; + if( argv[i][0]!='-' ){ + redraw = 1; + break; + } + c = argv[i][1]; + n = strlen(argv[i]); + if( c=='c' && n>4 && 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; i<N_COLOR; i++){ + if( htmlPtr->apColor[i] != 0 ){ + Tk_FreeColor(htmlPtr->apColor[i]); + htmlPtr->apColor[i] = 0; + } + } + for(i=0; i<N_COLOR; i++){ + htmlPtr->iDark[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; i<N_FONT; i++){ + if( htmlPtr->aFont[i] != 0 ){ + Tk_FreeFont(htmlPtr->aFont[i]); + htmlPtr->aFont[i] = 0; + } + } + for(i=0; i<Html_TypeCount; i++){ + if( htmlPtr->zHandler[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; i<N_CACHE_GC; i++, p++){ + if( p->index==0 ){ TestPoint(0); continue; } + if( (font<0 || p->font==font) && p->color==color ){ + if( p->index>1 ){ + for(j=0; j<N_CACHE_GC; j++){ + if( htmlPtr->aGcCache[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; i<N_CACHE_GC; i++, p++){ + if( p->index==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; j<N_CACHE_GC; j++){ + if( htmlPtr->aGcCache[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; i<N_CACHE_GC; i++, p++){ + if( p->index ){ 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; i<n; i++){ + if( !isxdigit(zColor[i]) ) break; + } + if( i==n ){ + sprintf(zAltColor,"#%s",zColor); + }else{ + strcpy(zAltColor, zColor); + } + name = Tk_GetUid(zAltColor); + }else{ + name = Tk_GetUid(zColor); + } + pNew = Tk_GetColor(htmlPtr->interp, 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; i<N_COLOR; i++){ + XColor *p = htmlPtr->apColor[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<<i); + Tk_FreeColor(q); + return i; + } + } + Tk_FreeColor(q); + + /* No exact matches. Look for a completely unused slot */ + for(i=N_PREDEFINED_COLOR; i<N_COLOR; i++){ + if( htmlPtr->apColor[i]==0 ){ + htmlPtr->apColor[i] = Tk_GetColorByValue(htmlPtr->clipwin, pRef); + htmlPtr->colorUsed |= (1<<i); + return i; + } + } + + /* No empty slots. Look for a slot that contains a color that + ** isn't currently in use. */ + for(i=N_PREDEFINED_COLOR; i<N_COLOR; i++){ + if( ((htmlPtr->colorUsed >> i) & 1) == 0 ){ + Tk_FreeColor(htmlPtr->apColor[i]); + htmlPtr->apColor[i] = Tk_GetColorByValue(htmlPtr->clipwin, pRef); + htmlPtr->colorUsed |= (1<<i); + return i; + } + } + + /* Ok, find the existing color that is closest to the color requested + ** and use it. */ + closest = 0; + closestDist = colorDistance(pRef, htmlPtr->apColor[0]); + for(i=1; i<N_COLOR; i++){ + dist = colorDistance(pRef, htmlPtr->apColor[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 <a> 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; i<nSubcommand; i++, pCmd++){ + if( pCmd->zCmd1==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; j<nSubcommand && (j==i || pCmd->zCmd1==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; j<nSubcommand && (j==i || aSubcommand[j].zCmd1==0); j++){ + Tcl_AppendResult(interp, " ", aSubcommand[j].zCmd2, 0); + TestPoint(0); + } + return TCL_ERROR; + } + } + if( argc<pCmd->minArgc || (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; i<nSubcommand; i++){ + if( aSubcommand[i].zCmd1==0 || aSubcommand[i].zCmd1[0]=='_' ){ + TestPoint(0); + continue; + } + Tcl_AppendResult(interp, " ", aSubcommand[i].zCmd1, 0); + TestPoint(0); + } + TestPoint(0); + return TCL_ERROR; +} + +/* +** The following routine implements the Tcl "html" command. This command +** is used to create new HTML widgets only. After the widget has been +** created, it is manipulated using the widget command defined above. +*/ +static int HtmlCommand( + ClientData clientData, /* Main window */ + Tcl_Interp *interp, /* Current interpreter. */ + int argc, /* Number of arguments. */ + const char **argv /* Argument strings. */ +){ + int n, c; + char *z; + + if (argc < 2) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " pathName ?options?\"", (char *) NULL); + return TCL_ERROR; + } + z = (char*)argv[1]; + n = strlen(z); + c = z[0]; + + /* If the first argument begins with ".", then it must be the + ** name of a new window the user wants to create. + */ + if( argv[1][0]=='.' ){ + HtmlWidget *htmlPtr; + Tk_Window new; + Tk_Window clipwin; + char *zClipwin; + Tk_Window tkwin = (Tk_Window)clientData; + static int varId = 1; /* Used to construct unique names */ + + new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL); + if (new == NULL) { + return TCL_ERROR; + } + zClipwin = HtmlAlloc( strlen(argv[1]) + 3 ); + if( zClipwin==0 ){ + Tk_DestroyWindow(new); + return TCL_ERROR; + } + sprintf(zClipwin,"%s.x",argv[1]); + clipwin = Tk_CreateWindowFromPath(interp, new, zClipwin, 0); + if( clipwin==0 ){ + Tk_DestroyWindow(new); + HtmlFree(zClipwin); + return TCL_ERROR; + } + + htmlPtr = HtmlAlloc(sizeof(HtmlWidget) + strlen(argv[1]) + 1); + memset(htmlPtr, 0, sizeof(HtmlWidget)); + htmlPtr->tkwin = 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; +} |