/*
** 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, 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 && 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;
}