source: trunk/gui/src/RpDiffview.c @ 4503

Last change on this file since 4503 was 3177, checked in by mmc, 12 years ago

Updated all of the copyright notices to reference the transfer to
the new HUBzero Foundation, LLC.

File size: 97.0 KB
Line 
1/*
2 * ----------------------------------------------------------------------
3 *  RpDiffview - like a textbox, but for showing diffs
4 *
5 *  Loads two strings, performs a diff, and shows the results.  Use
6 *  it as follows:
7 *
8 *  Rappture::Diffview .dv \
9 *      -addedbackground blue -addedforeground white \
10 *      -deletedbackground red -deletedforeground white -overstrike true \
11 *      -changedbackground black -changedforeground yellow \
12 *      -xscrollcommand {.x set} -yscrollcommand {.y set}
13 * 
14 *  scrollbar .x -orient horizontal -command {.dv xview}
15 *  scrollbar .y -orient vertical -command {.dv yview}
16 * 
17 *  .dv text 1 [exec cat /tmp/file1]
18 *  .dv text 2 [exec cat /tmp/file2]
19 *  .dv configure -diff 2->1 -layout sidebyside
20 * 
21 *  puts "DIFF OUTPUT:"
22 *  puts [.dv diffs -std]
23 *
24 * ======================================================================
25 *  AUTHOR:  Michael McLennan, Purdue University
26 *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
27 *
28 *  See the file "license.terms" for information on usage and
29 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
30 * ======================================================================
31 */
32#include "tk.h"
33#include <string.h>
34#include <stdlib.h>
35
36/*
37 * Special options for controlling diffs.
38 */
39enum diffdir {
40    DIFF_1TO2, DIFF_2TO1
41};
42static char *diffdirStrings[] = {
43    "1->2", "2->1", (char*)NULL
44};
45
46enum layoutStyle {
47    LAYOUT_INLINE, LAYOUT_SIDEBYSIDE
48};
49static char *layoutStyleStrings[] = {
50    "inline", "sidebyside", (char*)NULL
51};
52
53enum scancommand {
54    SCAN_MARK, SCAN_DRAGTO
55};
56static CONST char *scanCommandNames[] = {
57    "mark", "dragto", (char *) NULL
58};
59
60
61/*
62 * Data structure for line start/end for all lines in a buffer.
63 */
64typedef struct {
65    int numLines;               /* number of lines */
66    int maxLines;               /* max size of storage in startPtr/endPtr */
67    char **startPtr;            /* array of start pointers for each line */
68    int *lenPtr;                /* array of lengths for each line */
69} DiffviewLines;
70
71/*
72 * Data structure for text buffers within the widget:
73 */
74typedef struct {
75    Tcl_Obj *textObj;           /* actual text within this buffer */
76    DiffviewLines *lineLimits;  /* breakdown of textObj into line starts/ends */
77} DiffviewBuffer;
78
79/*
80 * Data structure used internally by diff routine:
81 */
82typedef struct subseq {
83    int index1;                 /* index in buffer #1 */
84    int index2;                 /* index in buffer #2 */
85    int next;                   /* next candidate match in the chain */
86} DiffviewSubseq;
87
88/*
89 * Data structure returned by diff routine:
90 */
91typedef struct {
92    int op;                     /* operation: 'a'=add 'd'=del 'c'=change */
93    int fromIndex1;             /* starts at this line in buffer 1 */
94    int toIndex1;               /* ends at this line in buffer 1 */
95    int fromIndex2;             /* starts at this line in buffer 2 */
96    int toIndex2;               /* ends at this line in buffer 2 */
97    int fromWorld;              /* starts at this line in world view */
98    int toWorld;                /* ends at this line in world view */
99} DiffviewDiffOp;
100
101typedef struct {
102    int maxDiffs;               /* maximum storage space for diffs */
103    int numDiffs;               /* number of diffs stored in ops */
104    DiffviewDiffOp *ops;        /* list of diffs */
105} DiffviewDiffs;
106
107/*
108 * Data structure used for each line of the final layout:
109 */
110typedef struct {
111    char style;                 /* layout style: 'a'=add, 'd'=del, etc. */
112    char bnum;                  /* show line from this buffer */
113    int bline;                  /* line number in buffer bnum */
114    int diffNum;                /* line is part of this diff */
115} DiffviewLayoutLine;
116
117typedef struct {
118    int maxLines;               /* maximum number of lines allocated */
119    int numLines;               /* current number of lines in layout */
120    DiffviewLayoutLine *lines;  /* info for all lines in world view */
121} DiffviewLayout;
122
123/*
124 * Data structure for the widget:
125 */
126typedef struct {
127    Tk_Window tkwin;            /* tk window for the widget */
128    Display *display;           /* display containing the widget */
129    Tcl_Interp *interp;         /* interpreter containing the widget command */
130    Tcl_Command widgetCmd;      /* token for the widget command */
131    Tk_OptionTable optionTable; /* configuration options */
132
133    /*
134     * Used to display the widget:
135     */
136    Tk_Cursor cursor;           /* cursor for the widget */
137    Tk_3DBorder normalBorder;   /* border around the widget */
138    int borderWidth;            /* width of the border in pixels */
139    int relief;                 /* relief: raised, sunken, etc. */
140    int highlightWidth;         /* width of border showing focus highlight */
141    XColor *highlightBgColor;   /* color for focus highlight bg */
142    XColor *highlightColor;     /* color for focus highlight */
143    int inset;                  /* width of all borders--offset to ul corner */
144
145    DiffviewBuffer buffer[2];   /* text to diff -- buffers #1 and #2 */
146    DiffviewDiffs *diffsPtr;    /* diff: longest common subseq btwn buffers */
147    DiffviewLayout worldview;   /* array mapping each real line in world
148                                 * coordinates to the corresponding line
149                                 * in buffer #1/#2, along with its style */
150
151    enum diffdir diffdir;       /* diff between these buffers */
152    enum layoutStyle layout;    /* layout style (side-by-side or inline) */
153    int maxWidth;               /* maximum width of world view in pixels */
154    int maxHeight;              /* maximum height of world view in pixels */
155    int lineHeight;             /* height of a line with current font */
156    int lineAscent;             /* ascent of a line with current font */
157    int topLine;                /* index of topmost line in view */
158    int btmLine;                /* index of bottommost line in view */
159    int fullLines;              /* number of full lines that fit in view */
160
161    int xOffset;                /* offset in pixels to left edge of view */
162    int xScrollUnit;            /* num pixels for one unit of horizontal
163                                 * scrolling (one average character) */
164    int yOffset;                /* offset in pixels to top edge of view */
165    int yScrollUnit;            /* num pixels for one unit of vertical
166                                 * scrolling (one average line) */
167    char *takeFocus;            /* value of -takefocus option */
168    char *yScrollCmd;           /* command prefix for scrolling */
169    char *xScrollCmd;           /* command prefix for scrolling */
170
171    int scanMarkX;              /* x-coord for "scan mark" */
172    int scanMarkY;              /* y-coord for "scan mark" */
173    int scanMarkXStart;         /* xOffset at start of scan */
174    int scanMarkYStart;         /* yOffset at start of scan */
175
176
177    Tk_Font tkfont;             /* text font for content of widget */
178    Tk_Font tklastfont;         /* previous text font (so we detect changes) */
179    XColor *fgColorPtr;         /* normal foreground color */
180    XColor *addBgColorPtr;      /* background for "added" text in diff */
181    XColor *addFgColorPtr;      /* foreground for "added" text in diff */
182    XColor *delBgColorPtr;      /* background for "deleted" text in diff */
183    XColor *delFgColorPtr;      /* foreground for "deleted" text in diff */
184    XColor *chgBgColorPtr;      /* background for "changed" text in diff */
185    XColor *chgFgColorPtr;      /* foreground for "changed" text in diff */
186    int overStrDel;             /* non-zero => overstrike deleted text */
187    GC normGC;                  /* GC for drawing normal text */
188    GC addFgGC;                 /* GC for drawing "added" text in diff */
189    GC delFgGC;                 /* GC for drawing "deleted" text in diff */
190    GC chgFgGC;                 /* GC for drawing "changed" text in diff */
191    int width;                  /* overall width of widget, in pixels */
192    int height;                 /* overall height of widget, in pixels */
193
194    int flags;                  /* flag bits for redraw, etc. */
195} Diffview;
196
197/*
198 * Tk widget flag bits:
199 *
200 * REDRAW_PENDING:      DoWhenIdle handler has been queued to redraw
201 * UPDATE_V_SCROLLBAR:  non-zero => vertical scrollbar needs updating
202 * UPDATE_H_SCROLLBAR:  non-zero => horizontal scrollbar needs updating
203 * GOT_FOCUS:           non-zero => widget has input focus
204 * FONT_CHANGED:        font changed, so layout needs to be recomputed
205 * WIDGET_DELETED:      widget has been destroyed, so don't update
206 */
207
208#define REDRAW_PENDING          1
209#define UPDATE_V_SCROLLBAR      2
210#define UPDATE_H_SCROLLBAR      4
211#define GOT_FOCUS               8
212#define FONT_CHANGED            16
213#define WIDGET_DELETED          32
214
215
216/*
217 * Default option values.
218 */
219#define BLACK                           "#000000"
220#define WHITE                           "#ffffff"
221#define NORMAL_BG                       "#d9d9d9"
222
223#define DEF_DIFFVIEW_ADDBG              "#ccffcc"
224#define DEF_DIFFVIEW_ADDFG              BLACK
225#define DEF_DIFFVIEW_BG_COLOR           NORMAL_BG
226#define DEF_DIFFVIEW_BG_MONO            WHITE
227#define DEF_DIFFVIEW_BORDERWIDTH        "2"
228#define DEF_DIFFVIEW_CHGBG              "#ffffcc"
229#define DEF_DIFFVIEW_CHGFG              BLACK
230#define DEF_DIFFVIEW_CURSOR             ""
231#define DEF_DIFFVIEW_DELBG              "#ffcccc"
232#define DEF_DIFFVIEW_DELFG              "#666666"
233#define DEF_DIFFVIEW_DIFF               "1->2"
234#define DEF_DIFFVIEW_FG                 BLACK
235#define DEF_DIFFVIEW_FONT               "Courier -12"
236#define DEF_DIFFVIEW_HEIGHT             "2i"
237#define DEF_DIFFVIEW_HIGHLIGHT_BG       NORMAL_BG
238#define DEF_DIFFVIEW_HIGHLIGHT          BLACK
239#define DEF_DIFFVIEW_HIGHLIGHT_WIDTH    "1"
240#define DEF_DIFFVIEW_LAYOUT             "inline"
241#define DEF_DIFFVIEW_OVERSTRDEL         "true"
242#define DEF_DIFFVIEW_RELIEF             "sunken"
243#define DEF_DIFFVIEW_TAKE_FOCUS         (char*)NULL
244#define DEF_DIFFVIEW_WIDTH              "2i"
245#define DEF_DIFFVIEW_SCROLL_CMD         ""
246
247/*
248 * Configuration options for this widget.
249 * (must be in alphabetical order)
250 */
251static Tk_OptionSpec optionSpecs[] = {
252    {TK_OPTION_COLOR, "-addedbackground", "addedBackground", "Background",
253         DEF_DIFFVIEW_ADDBG, -1, Tk_Offset(Diffview, addBgColorPtr),
254         TK_OPTION_NULL_OK, 0, 0},
255    {TK_OPTION_COLOR, "-addedforeground", "addedForeground", "Foreground",
256         DEF_DIFFVIEW_ADDFG, -1, Tk_Offset(Diffview, addFgColorPtr),
257         TK_OPTION_NULL_OK, 0, 0},
258    {TK_OPTION_BORDER, "-background", "background", "Background",
259         DEF_DIFFVIEW_BG_COLOR, -1, Tk_Offset(Diffview, normalBorder),
260         0, (ClientData) DEF_DIFFVIEW_BG_MONO, 0},
261    {TK_OPTION_SYNONYM, "-bd", (char*)NULL, (char*)NULL,
262         (char*)NULL, 0, -1, 0, (ClientData) "-borderwidth", 0},
263    {TK_OPTION_SYNONYM, "-bg", (char*)NULL, (char*)NULL,
264         (char*)NULL, 0, -1, 0, (ClientData) "-background", 0},
265    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
266         DEF_DIFFVIEW_BORDERWIDTH, -1, Tk_Offset(Diffview, borderWidth),
267         0, 0, 0},
268    {TK_OPTION_COLOR, "-changedbackground", "changedBackground", "Background",
269         DEF_DIFFVIEW_CHGBG, -1, Tk_Offset(Diffview, chgBgColorPtr),
270         TK_OPTION_NULL_OK, 0, 0},
271    {TK_OPTION_COLOR, "-changedforeground", "changedForeground", "Foreground",
272         DEF_DIFFVIEW_CHGFG, -1, Tk_Offset(Diffview, chgFgColorPtr),
273         TK_OPTION_NULL_OK, 0, 0},
274    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
275         DEF_DIFFVIEW_CURSOR, -1, Tk_Offset(Diffview, cursor),
276         TK_OPTION_NULL_OK, 0, 0},
277    {TK_OPTION_COLOR, "-deletedbackground", "deletedBackground", "Background",
278         DEF_DIFFVIEW_DELBG, -1, Tk_Offset(Diffview, delBgColorPtr),
279         TK_OPTION_NULL_OK, 0, 0},
280    {TK_OPTION_COLOR, "-deletedforeground", "deletedForeground", "Foreground",
281         DEF_DIFFVIEW_DELFG, -1, Tk_Offset(Diffview, delFgColorPtr),
282         TK_OPTION_NULL_OK, 0, 0},
283    {TK_OPTION_STRING_TABLE, "-diff", "diff", "Diff",
284        DEF_DIFFVIEW_DIFF, -1, Tk_Offset(Diffview, diffdir),
285        0, (ClientData)diffdirStrings, 0},
286    {TK_OPTION_SYNONYM, "-fg", "foreground", (char*)NULL,
287         (char*)NULL, 0, -1, 0, (ClientData) "-foreground", 0},
288    {TK_OPTION_FONT, "-font", "font", "Font",
289         DEF_DIFFVIEW_FONT, -1, Tk_Offset(Diffview, tkfont), 0, 0, 0},
290    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
291         DEF_DIFFVIEW_FG, -1, Tk_Offset(Diffview, fgColorPtr), 0, 0, 0},
292    {TK_OPTION_PIXELS, "-height", "height", "Height",
293         DEF_DIFFVIEW_HEIGHT, -1, Tk_Offset(Diffview, height), 0, 0, 0},
294    {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
295         "HighlightBackground", DEF_DIFFVIEW_HIGHLIGHT_BG, -1,
296         Tk_Offset(Diffview, highlightBgColor), 0, 0, 0},
297    {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
298         DEF_DIFFVIEW_HIGHLIGHT, -1, Tk_Offset(Diffview, highlightColor),
299         0, 0, 0},
300    {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
301         "HighlightThickness", DEF_DIFFVIEW_HIGHLIGHT_WIDTH, -1,
302         Tk_Offset(Diffview, highlightWidth), 0, 0, 0},
303    {TK_OPTION_STRING_TABLE, "-layout", "layout", "Layout",
304        DEF_DIFFVIEW_LAYOUT, -1, Tk_Offset(Diffview, layout),
305        0, (ClientData)layoutStyleStrings, 0},
306    {TK_OPTION_BOOLEAN, "-overstrike", "overstrike", "Overstrike",
307         DEF_DIFFVIEW_OVERSTRDEL, -1, Tk_Offset(Diffview, overStrDel), 0, 0, 0},
308    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
309         DEF_DIFFVIEW_RELIEF, -1, Tk_Offset(Diffview, relief), 0, 0, 0},
310    {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
311         DEF_DIFFVIEW_TAKE_FOCUS, -1, Tk_Offset(Diffview, takeFocus),
312         TK_OPTION_NULL_OK, 0, 0},
313    {TK_OPTION_PIXELS, "-width", "width", "Width",
314         DEF_DIFFVIEW_WIDTH, -1, Tk_Offset(Diffview, width), 0, 0, 0},
315    {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
316         DEF_DIFFVIEW_SCROLL_CMD, -1, Tk_Offset(Diffview, xScrollCmd),
317         TK_OPTION_NULL_OK, 0, 0},
318    {TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
319         DEF_DIFFVIEW_SCROLL_CMD, -1, Tk_Offset(Diffview, yScrollCmd),
320         TK_OPTION_NULL_OK, 0, 0},
321    {TK_OPTION_END, (char*)NULL, (char*)NULL, (char*)NULL,
322        (char*)NULL, 0, -1, 0, 0, 0}
323};
324
325/*
326 * Widget commands:
327 */
328static CONST char *commandNames[] = {
329    "bbox", "cget", "configure",
330    "diffs", "scan", "see",
331    "text", "xview", "yview",
332    (char*)NULL
333};
334
335enum command {
336    COMMAND_BBOX, COMMAND_CGET, COMMAND_CONFIGURE,
337    COMMAND_DIFFS, COMMAND_SCAN, COMMAND_SEE,
338    COMMAND_TEXT, COMMAND_XVIEW, COMMAND_YVIEW
339};
340
341/*
342 * Forward declarations for remaining procedures.
343 */
344static int              DiffviewObjCmd _ANSI_ARGS_((ClientData clientData,
345                            Tcl_Interp *interp, int objc,
346                            Tcl_Obj *CONST objv[]));
347static int              DiffviewWidgetObjCmd _ANSI_ARGS_((ClientData clientData,
348                            Tcl_Interp *interp, int objc,
349                            Tcl_Obj *CONST objv[]));
350static int              DiffviewBboxSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
351                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
352static int              DiffviewDiffsSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
353                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
354static int              DiffviewTextSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
355                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
356static int              DiffviewXviewSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
357                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
358static int              DiffviewYviewSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
359                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
360static int              DiffviewGetIndex _ANSI_ARGS_ ((Tcl_Interp *interp,
361                            Diffview *dvPtr, Tcl_Obj *objPtr, int *linePtr,
362                            DiffviewDiffOp **diffOpPtrPtr));
363
364static void             DestroyDiffview _ANSI_ARGS_((char *memPtr));
365static int              ConfigureDiffview _ANSI_ARGS_((Tcl_Interp *interp,
366                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[],
367                            int flags));
368static void             DiffviewWorldChanged _ANSI_ARGS_((
369                            ClientData instanceData));
370static void             EventuallyRedraw _ANSI_ARGS_((Diffview *dvPtr));
371static void             DisplayDiffview _ANSI_ARGS_((ClientData clientData));
372static void             DiffviewComputeGeometry _ANSI_ARGS_((Diffview *dvPtr));
373static void             DiffviewEventProc _ANSI_ARGS_((ClientData clientData,
374                            XEvent *eventPtr));
375static void             DiffviewCmdDeletedProc _ANSI_ARGS_((
376                            ClientData clientData));
377static void             ChangeDiffviewView _ANSI_ARGS_((Diffview *dvPtr,
378                            int lnum));
379static void             DiffviewScanTo _ANSI_ARGS_((Diffview *dvPtr,
380                            int x, int y));
381static void             DiffviewUpdateLayout _ANSI_ARGS_((
382                            Diffview *dvPtr));
383static void             DiffviewUpdateVScrollbar _ANSI_ARGS_((
384                            Diffview *dvPtr));
385static void             DiffviewUpdateHScrollbar _ANSI_ARGS_((
386                            Diffview *dvPtr));
387static DiffviewLines*   DiffviewLinesCreate _ANSI_ARGS_((char *textPtr,
388                            int textLen));
389static void             DiffviewLinesFree _ANSI_ARGS_((
390                            DiffviewLines *lineLimitsPtr));
391static DiffviewDiffs*   DiffviewDiffsCreate _ANSI_ARGS_((
392                            char *textPtr1, DiffviewLines *limsPtr1,
393                            char *textPtr2, DiffviewLines *limsPtr2));
394static void             DiffviewDiffsAppend _ANSI_ARGS_((
395                            DiffviewDiffs *diffsPtr, int op,
396                            int fromIndex1, int toIndex1,
397                            int fromIndex2, int toIndex2));
398static void             DiffviewDiffsFree _ANSI_ARGS_((
399                            DiffviewDiffs *diffsPtr));
400static void             DiffviewLayoutAdd _ANSI_ARGS_((
401                            DiffviewLayout *layoutPtr,
402                            DiffviewLayoutLine *linePtr));
403static void             DiffviewLayoutClear _ANSI_ARGS_((
404                            DiffviewLayout *layoutPtr));
405static void             DiffviewLayoutFree _ANSI_ARGS_((
406                            DiffviewLayout *layoutPtr));
407
408/*
409 * Standard Tk procs invoked from generic window code.
410 */
411static Tk_ClassProcs diffviewClass = {
412    sizeof(Tk_ClassProcs),      /* size */
413    DiffviewWorldChanged,       /* worldChangedProc */
414};
415
416/*
417 * ------------------------------------------------------------------------
418 *  RpDiffview_Init --
419 *
420 *  Invoked when the Rappture GUI library is being initialized
421 *  to install the "diffview" widget into the interpreter.
422 *
423 *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
424 *  message in the interp) if anything goes wrong.
425 * ------------------------------------------------------------------------
426 */
427int
428RpDiffview_Init(interp)
429    Tcl_Interp *interp;         /* interpreter being initialized */
430{
431    static char *script = "source [file join $RapptureGUI::library scripts diffview.tcl]";
432
433    /* install the widget command */
434    Tcl_CreateObjCommand(interp, "Rappture::Diffview", DiffviewObjCmd,
435        NULL, NULL);
436
437    /* load the default bindings */
438    if (Tcl_Eval(interp, script) != TCL_OK) {
439        return TCL_ERROR;
440    }
441
442    return TCL_OK;
443}
444
445/*
446 * ----------------------------------------------------------------------
447 * DiffviewObjCmd()
448 *
449 * Called whenever a Diffview object is created to create the widget
450 * and install the Tcl access command.
451 * ----------------------------------------------------------------------
452 */
453static int
454DiffviewObjCmd(clientData, interp, objc, objv)
455    ClientData clientData;        /* not used */
456    Tcl_Interp *interp;                /* current interpreter */
457    int objc;                        /* number of command arguments */
458    Tcl_Obj *CONST objv[];        /* command argument objects */
459{
460    Diffview *dvPtr;
461    Tk_Window tkwin;
462
463    if (objc < 2) {
464        Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
465        return TCL_ERROR;
466    }
467
468    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
469                Tcl_GetString(objv[1]), (char*)NULL);
470
471    if (tkwin == NULL) {
472        return TCL_ERROR;
473    }
474
475    /*
476     * Create a data structure for the widget objects and set good
477     * initial values.
478     */
479    dvPtr = (Diffview*)ckalloc(sizeof(Diffview));
480    memset((void*)dvPtr, 0, (sizeof(Diffview)));
481
482    dvPtr->tkwin   = tkwin;
483    dvPtr->display = Tk_Display(tkwin);
484    dvPtr->interp  = interp;
485    dvPtr->widgetCmd = Tcl_CreateObjCommand(interp,
486            Tk_PathName(dvPtr->tkwin), DiffviewWidgetObjCmd,
487            (ClientData)dvPtr, DiffviewCmdDeletedProc);
488    dvPtr->optionTable = Tk_CreateOptionTable(interp, optionSpecs);
489
490    dvPtr->buffer[0].textObj = NULL;
491    dvPtr->buffer[0].lineLimits = NULL;
492    dvPtr->buffer[1].textObj = NULL;
493    dvPtr->buffer[1].lineLimits = NULL;
494    dvPtr->diffdir = DIFF_1TO2;
495
496    dvPtr->cursor = None;
497    dvPtr->relief = TK_RELIEF_SUNKEN;
498    dvPtr->normGC = None;
499    dvPtr->addFgGC = None;
500    dvPtr->delFgGC = None;
501    dvPtr->chgFgGC = None;
502    dvPtr->xScrollUnit = 1;
503    dvPtr->yScrollUnit = 1;
504
505    /*
506     * Use reference counting to decide when this data should be
507     * cleaned up.
508     */
509    Tcl_Preserve((ClientData) dvPtr->tkwin);
510
511    Tk_SetClass(dvPtr->tkwin, "Diffview");
512    Tk_SetClassProcs(dvPtr->tkwin, &diffviewClass, (ClientData)dvPtr);
513
514    Tk_CreateEventHandler(dvPtr->tkwin,
515            ExposureMask|StructureNotifyMask|FocusChangeMask,
516            DiffviewEventProc, (ClientData)dvPtr);
517
518    if (Tk_InitOptions(interp, (char*)dvPtr, dvPtr->optionTable, tkwin)
519            != TCL_OK) {
520        Tk_DestroyWindow(dvPtr->tkwin);
521        return TCL_ERROR;
522    }
523
524    if (ConfigureDiffview(interp, dvPtr, objc-2, objv+2, 0) != TCL_OK) {
525        Tk_DestroyWindow(dvPtr->tkwin);
526        return TCL_ERROR;
527    }
528
529    Tcl_SetResult(interp, Tk_PathName(dvPtr->tkwin), TCL_STATIC);
530    return TCL_OK;
531}
532
533/*
534 * ----------------------------------------------------------------------
535 * DiffviewWidgetObjCmd()
536 *
537 * Called to process the operations associated with the widget.  Handles
538 * the widget access command, using the operations defined in the
539 * commandNames[] array.
540 * ----------------------------------------------------------------------
541 */
542static int
543DiffviewWidgetObjCmd(clientData, interp, objc, objv)
544    ClientData clientData;    /* widget data */
545    Tcl_Interp *interp;       /* interp handling this command */
546    int objc;                 /* number of command arguments */
547    Tcl_Obj *CONST objv[];    /* command arguments */
548{
549    Diffview *dvPtr = (Diffview*)clientData;
550    int result = TCL_OK;
551    int cmdToken;
552   
553    if (objc < 2) {
554        Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
555        return TCL_ERROR;
556    }
557
558    /*
559     * Look up the command option and map it to an enumerated value.
560     */
561    result = Tcl_GetIndexFromObj(interp, objv[1], commandNames,
562            "option", 0, &cmdToken);
563
564    if (result != TCL_OK) {
565        return result;
566    }
567
568    /* hang on to the widget data until we exit... */
569    Tcl_Preserve((ClientData)dvPtr);
570
571    switch (cmdToken) {
572        case COMMAND_BBOX: {
573            result = DiffviewBboxSubCmd(interp, dvPtr, objc, objv);
574            break;
575        }
576
577        case COMMAND_CGET: {
578            Tcl_Obj *objPtr;
579            if (objc != 3) {
580                Tcl_WrongNumArgs(interp, 2, objv, "option");
581                result = TCL_ERROR;
582                break;
583            }
584
585            objPtr = Tk_GetOptionValue(interp, (char*)dvPtr,
586                    dvPtr->optionTable, objv[2], dvPtr->tkwin);
587            if (objPtr == NULL) {
588                result = TCL_ERROR;
589                break;
590            }
591            Tcl_SetObjResult(interp, objPtr);
592            result = TCL_OK;
593            break;
594        }
595       
596        case COMMAND_CONFIGURE: {
597            Tcl_Obj *objPtr;
598            if (objc <= 3) {
599                /* query the value of an option */
600                objPtr = Tk_GetOptionInfo(interp, (char*)dvPtr,
601                        dvPtr->optionTable,
602                        (objc == 3) ? objv[2] : (Tcl_Obj*)NULL,
603                        dvPtr->tkwin);
604                if (objPtr == NULL) {
605                    result = TCL_ERROR;
606                    break;
607                } else {
608                    Tcl_SetObjResult(interp, objPtr);
609                    result = TCL_OK;
610                }
611            } else {
612                /* set one or more configuration options */
613                result = ConfigureDiffview(interp, dvPtr, objc-2, objv+2, 0);
614            }
615            break;
616        }
617
618        case COMMAND_DIFFS: {
619            result = DiffviewDiffsSubCmd(interp, dvPtr, objc, objv);
620            break;
621        }
622
623        case COMMAND_SCAN: {
624            int x, y, scanCmdIndex;
625
626            if (objc != 5) {
627                Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x y");
628                result = TCL_ERROR;
629                break;
630            }
631
632            if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK
633                    || Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
634                result = TCL_ERROR;
635                break;
636            }
637
638            result = Tcl_GetIndexFromObj(interp, objv[2], scanCommandNames,
639                    "option", 0, &scanCmdIndex);
640            if (result != TCL_OK) {
641                break;
642            }
643            switch (scanCmdIndex) {
644                case SCAN_MARK: {
645                    dvPtr->scanMarkX = x;
646                    dvPtr->scanMarkY = y;
647                    dvPtr->scanMarkXStart = dvPtr->xOffset;
648                    dvPtr->scanMarkYStart = dvPtr->yOffset;
649                    break;
650                }
651                case SCAN_DRAGTO: {
652                    DiffviewScanTo(dvPtr, x, y);
653                    break;
654                }
655            }
656            result = TCL_OK;
657            break;
658        }
659
660        case COMMAND_SEE: {
661            DiffviewDiffOp *diffOpPtr;
662            int line1, line2, topline;
663
664            if (objc != 3) {
665                Tcl_WrongNumArgs(interp, 2, objv, "index");
666                result = TCL_ERROR;
667                break;
668            }
669            result = DiffviewGetIndex(interp, dvPtr, objv[2],
670                &line1, &diffOpPtr);
671            if (result != TCL_OK) {
672                break;
673            }
674
675            if (line1 < 0 && diffOpPtr != NULL) {
676                line1 = diffOpPtr->fromWorld;
677                line2 = diffOpPtr->toWorld;
678            } else {
679                line2 = line1;
680            }
681
682            if (line1 >= dvPtr->worldview.numLines) {
683                line1 = line2 = dvPtr->worldview.numLines-1;
684            }
685            if (line1 < 0) {
686                line1 = line2 = 0;
687            }
688
689            if (line1 < dvPtr->topLine || line1 > dvPtr->btmLine) {
690                /* location off screen? then center it in y-view */
691                if (line2-line1 >= dvPtr->fullLines) {
692                    topline = line1;
693                } else {
694                    topline = (line1+line2)/2 - dvPtr->fullLines/2;
695                    if (topline < 0) topline = 0;
696                }
697                ChangeDiffviewView(dvPtr, topline);
698            }
699            result = TCL_OK;
700            break;
701        }
702
703        case COMMAND_TEXT: {
704            result = DiffviewTextSubCmd(interp, dvPtr, objc, objv);
705            break;
706        }
707
708        case COMMAND_XVIEW: {
709            result = DiffviewXviewSubCmd(interp, dvPtr, objc, objv);
710            break;
711        }
712       
713        case COMMAND_YVIEW: {
714            result = DiffviewYviewSubCmd(interp, dvPtr, objc, objv);
715            break;
716        }
717    }
718
719    /* okay, release the widget data and return */
720    Tcl_Release((ClientData)dvPtr);
721    return result;
722}
723
724/*
725 * ----------------------------------------------------------------------
726 * DiffviewBboxSubCmd()
727 *
728 * Handles the "bbox" operation on the widget.  Returns a bounding
729 * box for the specified part of the widget.
730 * ----------------------------------------------------------------------
731 */
732static int
733DiffviewBboxSubCmd(interp, dvPtr, objc, objv)
734    Tcl_Interp *interp;       /* interp handling this command */
735    Diffview *dvPtr;          /* widget data */
736    int objc;                 /* number of command arguments */
737    Tcl_Obj *CONST objv[];    /* command arguments */
738{
739    char *opt, buf[256];
740    DiffviewDiffOp *diffOpPtr;
741    int line, bnum, bline, x1, y0, y1, wd;
742    char *textPtr; int textLen;
743
744    if (objc != 3) {
745        Tcl_WrongNumArgs(interp, 2, objv, "index");
746        return TCL_ERROR;
747    }
748
749    /* make sure that our layout info is up to date for queries below */
750    DiffviewUpdateLayout(dvPtr);
751
752    opt = Tcl_GetString(objv[2]);
753    if (*opt == 'a' && strcmp(opt,"all") == 0) {
754        sprintf(buf, "0 0 %d %d", dvPtr->maxWidth, dvPtr->maxHeight);
755        Tcl_SetResult(interp, buf, TCL_VOLATILE);
756        return TCL_OK;
757    }
758
759    if (DiffviewGetIndex(interp, dvPtr, objv[2], &line, &diffOpPtr) != TCL_OK) {
760        Tcl_AppendResult(interp, " or all", (char*)NULL);
761        return TCL_ERROR;
762    }
763
764    /* return the bounding box for a line in world coords */
765    if (line >= 0) {
766        y0 = line*dvPtr->lineHeight;
767        y1 = (line+1)*dvPtr->lineHeight;
768        x1 = 0;
769        if (line < dvPtr->worldview.numLines) {
770            bnum = dvPtr->worldview.lines[line].bnum;
771            bline = dvPtr->worldview.lines[line].bline;
772            if (bline >= 0) {
773                textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
774                textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
775                x1 = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
776            }
777        }
778        sprintf(buf, "0 %d %d %d", y0, x1, y1);
779        Tcl_SetResult(interp, buf, TCL_VOLATILE);
780        return TCL_OK;
781    }
782
783    /* return the bounding box for a diff in world coords */
784    if (diffOpPtr) {
785        y0 = diffOpPtr->fromWorld*dvPtr->lineHeight;
786        y1 = (diffOpPtr->toWorld+1)*dvPtr->lineHeight;
787        x1 = 0;
788
789        for (line=diffOpPtr->fromWorld; line <= diffOpPtr->toWorld; line++) {
790            if (line < dvPtr->worldview.numLines) {
791                bnum = dvPtr->worldview.lines[line].bnum;
792                bline = dvPtr->worldview.lines[line].bline;
793                if (bline >= 0) {
794                    textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
795                    textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
796                    wd = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
797                    if (wd > x1) {
798                        x1 = wd;
799                    }
800                }
801            }
802        }
803        sprintf(buf, "0 %d %d %d", y0, x1, y1);
804        Tcl_SetResult(interp, buf, TCL_VOLATILE);
805    }
806    return TCL_OK;
807}
808
809/*
810 * ----------------------------------------------------------------------
811 * DiffviewDiffsSubCmd()
812 *
813 * Handles the "diffs" operation on the widget with the following
814 * syntax:
815 *
816 *   widget diffs ?-std|-debug|-number?
817 *
818 * The default is "diffs -std", which returns the usual output from
819 * the Unix "diff" command.  The -debug option returns a more detailed
820 * output that is useful for testing/debugging.  The -number option
821 * returns the total number of diffs.  This is useful for commands
822 * such as "see #2", where you can bring a particular diff into view.
823 * ----------------------------------------------------------------------
824 */
825static int
826DiffviewDiffsSubCmd(interp, dvPtr, objc, objv)
827    Tcl_Interp *interp;       /* interp handling this command */
828    Diffview *dvPtr;          /* widget data */
829    int objc;                 /* number of command arguments */
830    Tcl_Obj *CONST objv[];    /* command arguments */
831{
832    Tcl_Obj *resultPtr;
833    char range1[128], range2[128];
834    int i, n;
835
836    static CONST char *options[] = {
837        "-std",         "-debug",       "-number",      (char*)NULL
838    };
839    enum options {
840        DIFFS_STD,      DIFFS_DEBUG,    DIFFS_NUMBER
841    };
842    int op = DIFFS_STD;
843
844
845    if (objc > 3) {
846        Tcl_WrongNumArgs(interp, 2, objv, "?-std? ?-debug? ?-number?");
847        return TCL_ERROR;
848    }
849
850    /* decode the option that controls the return result */
851    if (objc > 2) {
852        if (Tcl_GetIndexFromObj(interp, objv[2], options, "option", 0,
853                &op) != TCL_OK) {
854            return TCL_ERROR;
855        }
856    }
857
858    /* make sure that our layout info is up to date for queries below */
859    DiffviewUpdateLayout(dvPtr);
860
861    switch ((enum options)op) {
862        case DIFFS_STD: {
863            DiffviewDiffOp curr, *currOpPtr;
864            DiffviewLines *lines1, *lines2;
865
866            if (dvPtr->diffsPtr) {
867                resultPtr = Tcl_GetObjResult(interp);
868                for (i=0; i < dvPtr->diffsPtr->numDiffs; i++) {
869                    currOpPtr = &dvPtr->diffsPtr->ops[i];
870
871                    /* if we're diff'ing the other way, reverse the diff */
872                    if (dvPtr->diffdir == DIFF_1TO2) {
873                        memcpy((VOID*)&curr, (VOID*)currOpPtr,
874                            sizeof(DiffviewDiffOp));
875                        lines1 = dvPtr->buffer[0].lineLimits;
876                        lines2 = dvPtr->buffer[1].lineLimits;
877                    } else {
878                        switch (currOpPtr->op) {
879                            case 'a': curr.op = 'd'; break;
880                            case 'd': curr.op = 'a'; break;
881                            default:  curr.op = currOpPtr->op; break;
882                        }
883                        curr.fromIndex1 = currOpPtr->fromIndex2;
884                        curr.toIndex1   = currOpPtr->toIndex2;
885                        curr.fromIndex2 = currOpPtr->fromIndex1;
886                        curr.toIndex2   = currOpPtr->toIndex1;
887                        lines1 = dvPtr->buffer[1].lineLimits;
888                        lines2 = dvPtr->buffer[0].lineLimits;
889                    }
890
891                    /* append the diff info onto the output */
892                    if (curr.fromIndex1 == curr.toIndex1) {
893                        if (curr.op == 'a') {
894                            /* for append, use first index at raw value */
895                            sprintf(range1, "%d", curr.fromIndex1);
896                        } else {
897                            sprintf(range1, "%d", curr.fromIndex1+1);
898                        }
899                    } else {
900                        sprintf(range1, "%d,%d", curr.fromIndex1+1,
901                            curr.toIndex1+1);
902                    }
903
904                    if (curr.fromIndex2 == curr.toIndex2) {
905                        if (curr.op == 'd') {
906                            /* for delete, use second index at raw value */
907                            sprintf(range2, "%d", curr.fromIndex2);
908                        } else {
909                            sprintf(range2, "%d", curr.fromIndex2+1);
910                        }
911                    } else {
912                        sprintf(range2, "%d,%d", curr.fromIndex2+1,
913                            curr.toIndex2+1);
914                    }
915
916                    switch (curr.op) {
917                        case 'a': {
918                            Tcl_AppendToObj(resultPtr, range1, -1);
919                            Tcl_AppendToObj(resultPtr, "a", 1);
920                            Tcl_AppendToObj(resultPtr, range2, -1);
921                            Tcl_AppendToObj(resultPtr, "\n", 1);
922                            for (n=curr.fromIndex2; n <= curr.toIndex2; n++) {
923                                Tcl_AppendToObj(resultPtr, "> ", 2);
924                                Tcl_AppendToObj(resultPtr, lines2->startPtr[n],
925                                    lines2->lenPtr[n]);
926                                Tcl_AppendToObj(resultPtr, "\n", 1);
927                            }
928                            break;
929                        }
930                        case 'd': {
931                            Tcl_AppendToObj(resultPtr, range1, -1);
932                            Tcl_AppendToObj(resultPtr, "d", 1);
933                            Tcl_AppendToObj(resultPtr, range2, -1);
934                            Tcl_AppendToObj(resultPtr, "\n", 1);
935                            for (n=curr.fromIndex1; n <= curr.toIndex1; n++) {
936                                Tcl_AppendToObj(resultPtr, "< ", 2);
937                                Tcl_AppendToObj(resultPtr, lines1->startPtr[n],
938                                    lines1->lenPtr[n]);
939                                Tcl_AppendToObj(resultPtr, "\n", 1);
940                            }
941                            break;
942                        }
943                        case 'c': {
944                            Tcl_AppendToObj(resultPtr, range1, -1);
945                            Tcl_AppendToObj(resultPtr, "c", 1);
946                            Tcl_AppendToObj(resultPtr, range2, -1);
947                            Tcl_AppendToObj(resultPtr, "\n", 1);
948                            for (n=curr.fromIndex1; n <= curr.toIndex1; n++) {
949                                Tcl_AppendToObj(resultPtr, "< ", 2);
950                                Tcl_AppendToObj(resultPtr, lines1->startPtr[n],
951                                    lines1->lenPtr[n]);
952                                Tcl_AppendToObj(resultPtr, "\n", 1);
953                            }
954                            Tcl_AppendToObj(resultPtr, "---\n", 4);
955                            for (n=curr.fromIndex2; n <= curr.toIndex2; n++) {
956                                Tcl_AppendToObj(resultPtr, "> ", 2);
957                                Tcl_AppendToObj(resultPtr, lines2->startPtr[n],
958                                    lines2->lenPtr[n]);
959                                Tcl_AppendToObj(resultPtr, "\n", 1);
960                            }
961                            break;
962                        }
963                    }
964                }
965            }
966            break;
967        }
968        case DIFFS_DEBUG: {
969            DiffviewDiffOp *c;
970            char elem[256];
971
972            if (dvPtr->diffsPtr) {
973                for (i=0; i < dvPtr->diffsPtr->numDiffs; i++) {
974                    c = &dvPtr->diffsPtr->ops[i];
975
976                    /* append the diff info onto the output */
977                    if (c->fromIndex1 == c->toIndex1) {
978                        sprintf(range1, "%d", c->fromIndex1);
979                    } else {
980                        sprintf(range1, "%d,%d", c->fromIndex1, c->toIndex1);
981                    }
982
983                    if (c->fromIndex2 == c->toIndex2) {
984                        sprintf(range2, "%d", c->fromIndex2);
985                    } else {
986                        sprintf(range2, "%d,%d", c->fromIndex2, c->toIndex2);
987                    }
988
989                    sprintf(elem, "%c %s %s", c->op, range1, range2);
990                    Tcl_AppendElement(interp, elem);
991                }
992            }
993            break;
994        }
995        case DIFFS_NUMBER: {
996            int num = 0;
997            if (dvPtr->diffsPtr) {
998                num = dvPtr->diffsPtr->numDiffs;
999            }
1000            resultPtr = Tcl_NewIntObj(num);
1001            Tcl_SetObjResult(interp, resultPtr);
1002            break;
1003        }
1004    }
1005    return TCL_OK;
1006}
1007
1008/*
1009 * ----------------------------------------------------------------------
1010 * DiffviewTextSubCmd()
1011 *
1012 * Handles the "text" operation on the widget with the following
1013 * syntax:
1014 *
1015 *   widget text 1 ?string?
1016 *   widget text 2 ?string?
1017 *
1018 * Used to get/set the text being diff'd and displayed in the widget.
1019 * ----------------------------------------------------------------------
1020 */
1021static int
1022DiffviewTextSubCmd(interp, dvPtr, objc, objv)
1023    Tcl_Interp *interp;       /* interp handling this command */
1024    Diffview *dvPtr;          /* widget data */
1025    int objc;                 /* number of command arguments */
1026    Tcl_Obj *CONST objv[];    /* command arguments */
1027{
1028    int bufferNum;
1029    DiffviewBuffer *slotPtr;
1030
1031    if (objc < 2 || objc > 4) {
1032        Tcl_WrongNumArgs(interp, 2, objv, "bufferNum ?string?");
1033        return TCL_ERROR;
1034    }
1035
1036    /* decode the buffer number -- right now only buffers 1 & 2 */
1037    if (Tcl_GetIntFromObj(interp, objv[2], &bufferNum) != TCL_OK) {
1038        return TCL_ERROR;
1039    }
1040    if (bufferNum < 1 || bufferNum > 2) {
1041        Tcl_AppendResult(interp, "bad buffer number \"",
1042            Tcl_GetString(objv[2]), "\": should be 1 or 2",
1043            (char*)NULL);
1044        return TCL_ERROR;
1045    }
1046    slotPtr = &dvPtr->buffer[bufferNum-1];
1047
1048    if (objc == 3) {
1049        /* return current contents of the buffer */
1050        if (slotPtr->textObj != NULL) {
1051            Tcl_SetObjResult(interp, slotPtr->textObj);
1052        } else {
1053            Tcl_ResetResult(interp);
1054        }
1055        return TCL_OK;
1056    }
1057
1058    /* set and return the new contents of the buffer */
1059    if (slotPtr->textObj != NULL) {
1060        Tcl_DecrRefCount(slotPtr->textObj);
1061        DiffviewLinesFree(slotPtr->lineLimits);
1062        slotPtr->lineLimits = NULL;
1063    }
1064    slotPtr->textObj = objv[3];
1065    Tcl_IncrRefCount(slotPtr->textObj);
1066
1067    EventuallyRedraw(dvPtr);
1068
1069    Tcl_SetObjResult(interp, slotPtr->textObj);
1070    return TCL_OK;
1071}
1072
1073/*
1074 * ----------------------------------------------------------------------
1075 * DiffviewXviewSubCmd()
1076 *
1077 * Handles the "xview" operation on the widget.  Adjusts the x-axis view
1078 * according to some request, which usually comes from a scrollbar.
1079 * Supports the standard Tk operations for "xview".
1080 * ----------------------------------------------------------------------
1081 */
1082static int
1083DiffviewXviewSubCmd(interp, dvPtr, objc, objv)
1084    Tcl_Interp *interp;       /* interp handling this command */
1085    Diffview *dvPtr;          /* widget data */
1086    int objc;                 /* number of command arguments */
1087    Tcl_Obj *CONST objv[];    /* command arguments */
1088{
1089
1090    int offset = 0;
1091    int index, count, type, viewWidth, windowUnits, maxOffset;
1092    double fraction, fraction2;
1093   
1094    viewWidth = Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset;
1095
1096    if (objc == 2) {
1097        /*
1098         * COMMAND: widget xview
1099         * return current limits as fractions 0-1
1100         */
1101        if (dvPtr->maxWidth == 0) {
1102            Tcl_SetResult(interp, "0 1", TCL_STATIC);
1103        } else {
1104            char buf[TCL_DOUBLE_SPACE*2+2];
1105
1106            fraction = dvPtr->xOffset/((double)dvPtr->maxWidth);
1107            fraction2 = (dvPtr->xOffset + viewWidth)
1108                /((double)dvPtr->maxWidth);
1109            if (fraction2 > 1.0) {
1110                fraction2 = 1.0;
1111            }
1112            sprintf(buf, "%g %g", fraction, fraction2);
1113            Tcl_SetResult(interp, buf, TCL_VOLATILE);
1114        }
1115    } else {
1116        if (objc == 3) {
1117            /*
1118             * COMMAND: widget xview index
1119             * set left edge to character at index
1120             */
1121            if (Tcl_GetIntFromObj(interp, objv[2], &index) != TCL_OK) {
1122                return TCL_ERROR;
1123            }
1124            offset = index*dvPtr->xScrollUnit;
1125        } else {
1126            /*
1127             * COMMAND: widget xview moveto fraction
1128             * COMMAND: widget xview scroll number what
1129             * handles more complex scrolling movements
1130             */
1131            type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
1132            switch (type) {
1133                case TK_SCROLL_ERROR:
1134                    return TCL_ERROR;
1135                case TK_SCROLL_MOVETO:
1136                    offset = (int)(fraction*dvPtr->maxWidth + 0.5);
1137                    break;
1138                case TK_SCROLL_PAGES:
1139                    windowUnits = viewWidth/dvPtr->xScrollUnit;
1140                    if (windowUnits > 2) {
1141                        offset = dvPtr->xOffset
1142                            + count*dvPtr->xScrollUnit*(windowUnits-2);
1143                    } else {
1144                        offset = dvPtr->xOffset + count*dvPtr->xScrollUnit;
1145                    }
1146                    break;
1147                case TK_SCROLL_UNITS:
1148                    offset = dvPtr->xOffset + count*dvPtr->xScrollUnit;
1149                    break;
1150            }
1151        }
1152        maxOffset = dvPtr->maxWidth
1153            - (Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset)
1154            + (dvPtr->xScrollUnit - 1);
1155        if (offset > maxOffset) { offset = maxOffset; }
1156        if (offset < 0) { offset = 0; }
1157
1158        /* nudge back to an even boundary */
1159        offset -= offset % dvPtr->xScrollUnit;
1160
1161        if (offset != dvPtr->xOffset) {
1162            dvPtr->xOffset = offset;
1163            dvPtr->flags |= UPDATE_H_SCROLLBAR;
1164            EventuallyRedraw(dvPtr);
1165        }
1166    }
1167    return TCL_OK;
1168}
1169
1170/*
1171 * ----------------------------------------------------------------------
1172 * DiffviewYviewSubCmd()
1173 *
1174 * Handles the "yview" operation on the widget.  Adjusts the y-axis view
1175 * according to some request, which usually comes from a scrollbar.
1176 * Supports the standard Tk operations for "yview".
1177 * ----------------------------------------------------------------------
1178 */
1179static int
1180DiffviewYviewSubCmd(interp, dvPtr, objc, objv)
1181    Tcl_Interp *interp;       /* interp handling this command */
1182    Diffview *dvPtr;          /* widget data */
1183    int objc;                 /* number of command arguments */
1184    Tcl_Obj *CONST objv[];    /* command arguments */
1185{
1186    int index, yLines, count, type;
1187    double fraction, fraction2;
1188
1189    /* make sure that our layout info is up to date */
1190    DiffviewUpdateLayout(dvPtr);
1191
1192    if (objc == 2) {
1193        /*
1194         * COMMAND: widget yview
1195         * return current limits as fractions 0-1
1196         */
1197        if (dvPtr->maxHeight == 0) {
1198            Tcl_SetResult(interp, "0 1", TCL_STATIC);
1199        } else {
1200            char buf[TCL_DOUBLE_SPACE * 2+2];
1201
1202            yLines = dvPtr->maxHeight/dvPtr->lineHeight;
1203            fraction = dvPtr->topLine/((double)yLines);
1204            fraction2 = (dvPtr->topLine+dvPtr->fullLines)/((double)yLines);
1205            if (fraction2 > 1.0) {
1206                fraction2 = 1.0;
1207            }
1208            sprintf(buf, "%g %g", fraction, fraction2);
1209            Tcl_SetResult(interp, buf, TCL_VOLATILE);
1210        }
1211    } else if (objc == 3) {
1212        /*
1213         * COMMAND: widget yview index
1214         * set top edge to line at index
1215         */
1216        if (Tcl_GetIntFromObj(interp, objv[2], &index) != TCL_OK) {
1217            return TCL_ERROR;
1218        }
1219        ChangeDiffviewView(dvPtr, index);
1220    } else {
1221        /*
1222         * COMMAND: widget yview moveto fraction
1223         * COMMAND: widget yview scroll number what
1224         * handles more complex scrolling movements
1225         */
1226        type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
1227        switch (type) {
1228            case TK_SCROLL_ERROR:
1229                return TCL_ERROR;
1230            case TK_SCROLL_MOVETO:
1231                yLines = dvPtr->maxHeight/dvPtr->lineHeight;
1232                index = (int)(yLines*fraction + 0.5);
1233                break;
1234            case TK_SCROLL_PAGES:
1235                yLines = dvPtr->maxHeight/dvPtr->lineHeight;
1236                if (dvPtr->fullLines > 2) {
1237                    index = dvPtr->topLine + count*(dvPtr->fullLines-2);
1238                } else {
1239                    index = dvPtr->topLine + count;
1240                }
1241                break;
1242            case TK_SCROLL_UNITS:
1243                index = dvPtr->topLine + count;
1244                break;
1245        }
1246        ChangeDiffviewView(dvPtr, index);
1247    }
1248    return TCL_OK;
1249}
1250
1251/*
1252 * ----------------------------------------------------------------------
1253 * DiffviewGetIndex()
1254 *
1255 * Used by many of the widget operations to parse an index into the
1256 * widget view.  Handles the following syntax:
1257 *   #NN ...... particular diff number, starting from 1
1258 *   NN ....... particular line number in world view, starting from 0
1259 *
1260 * Returns TCL_OK if successful, and TCL_ERROR (along with an error
1261 * message) if anything goes wrong.
1262 * ----------------------------------------------------------------------
1263 */
1264static int
1265DiffviewGetIndex(interp, dvPtr, objPtr, linePtr, diffOpPtrPtr)
1266    Tcl_Interp *interp;             /* interp handling this command */
1267    Diffview *dvPtr;                /* widget data */
1268    Tcl_Obj *objPtr;                /* argument being parsed */
1269    int *linePtr;                   /* returns: line number or -1 */
1270    DiffviewDiffOp **diffOpPtrPtr;  /* returns: specific diff or NULL */
1271{
1272    int result = TCL_OK;
1273    char *opt = Tcl_GetString(objPtr);
1274    char *tail;
1275    int dnum;
1276
1277    /* clear the return values */
1278    if (linePtr) *linePtr = -1;
1279    if (diffOpPtrPtr) *diffOpPtrPtr = NULL;
1280
1281    if (*opt == '#') {
1282        dnum = strtol(opt+1, &tail, 10);
1283        if (*tail != '\0' || dnum <= 0) {
1284           result = TCL_ERROR;
1285        }
1286        else if (dvPtr->diffsPtr == NULL
1287                   || dnum > dvPtr->diffsPtr->numDiffs) {
1288           Tcl_AppendResult(interp, "diff \"", opt, "\" doesn't exist",
1289               (char*)NULL);
1290           return TCL_ERROR;
1291        }
1292        *diffOpPtrPtr = &dvPtr->diffsPtr->ops[dnum-1];
1293    } else {
1294        result = Tcl_GetIntFromObj(interp, objPtr, linePtr);
1295    }
1296
1297    if (result != TCL_OK) {
1298        Tcl_ResetResult(interp);
1299        Tcl_AppendResult(interp, "bad index \"", opt, "\": should be "
1300            "\"#n\" for diff, or an integer value for line number",
1301            (char*)NULL);
1302    }
1303    return result;
1304}
1305
1306/*
1307 * ----------------------------------------------------------------------
1308 * DestroyDiffview()
1309 *
1310 * Used by Tcl_Release/Tcl_EventuallyFree to clean up the data associated
1311 * with a widget when it is no longer being used.  This happens at the
1312 * end of a widget destruction sequence.
1313 * ----------------------------------------------------------------------
1314 */
1315static void
1316DestroyDiffview(memPtr)
1317    char *memPtr;             /* widget data */
1318{
1319    Diffview *dvPtr = (Diffview*)memPtr;
1320    int bnum;
1321
1322    /*
1323     * Clean up text associated with the diffs.
1324     */
1325    for (bnum=0; bnum < 2; bnum++) {
1326        if (dvPtr->buffer[bnum].textObj != NULL) {
1327            Tcl_DecrRefCount(dvPtr->buffer[bnum].textObj);
1328        }
1329        if (dvPtr->buffer[bnum].lineLimits != NULL) {
1330            DiffviewLinesFree(dvPtr->buffer[bnum].lineLimits);
1331        }
1332    }
1333
1334    if (dvPtr->diffsPtr) {
1335        DiffviewDiffsFree(dvPtr->diffsPtr);
1336    }
1337    DiffviewLayoutFree(&dvPtr->worldview);
1338
1339    /*
1340     * Free up GCs and configuration options.
1341     */
1342    if (dvPtr->normGC != None) {
1343        Tk_FreeGC(dvPtr->display, dvPtr->normGC);
1344    }
1345    if (dvPtr->addFgGC != None) {
1346        Tk_FreeGC(dvPtr->display, dvPtr->addFgGC);
1347    }
1348    if (dvPtr->delFgGC != None) {
1349        Tk_FreeGC(dvPtr->display, dvPtr->delFgGC);
1350    }
1351    if (dvPtr->chgFgGC != None) {
1352        Tk_FreeGC(dvPtr->display, dvPtr->chgFgGC);
1353    }
1354
1355    Tk_FreeConfigOptions((char*)dvPtr, dvPtr->optionTable, dvPtr->tkwin);
1356    Tcl_Release((ClientData) dvPtr->tkwin);
1357    dvPtr->tkwin = NULL;
1358    ckfree((char*)dvPtr);
1359}
1360
1361/*
1362 * ----------------------------------------------------------------------
1363 * ConfigureDiffview()
1364 *
1365 * Takes a list of configuration options in objc/objv format and applies
1366 * the settings to the widget.  This is the underlying "configure"
1367 * operation for the widget.
1368 * ----------------------------------------------------------------------
1369 */
1370static int
1371ConfigureDiffview(interp, dvPtr, objc, objv, flags)
1372    Tcl_Interp *interp;       /* interp handling this command */
1373    Diffview *dvPtr;          /* widget data */
1374    int objc;                 /* number of configuration option words */
1375    Tcl_Obj *CONST objv[];    /* configuration option words */
1376    int flags;                /* flags for Tk_ConfigureWidget */
1377{
1378    Tcl_Obj *errorResult = NULL;
1379    int status;
1380    Tk_SavedOptions savedOptions;
1381
1382    status = Tk_SetOptions(interp, (char*)dvPtr, dvPtr->optionTable,
1383        objc, objv, dvPtr->tkwin, &savedOptions, (int*)NULL);
1384
1385    if (status != TCL_OK) {
1386        errorResult = Tcl_GetObjResult(interp);
1387        Tcl_IncrRefCount(errorResult);
1388        Tk_RestoreSavedOptions(&savedOptions);
1389
1390        Tcl_SetObjResult(interp, errorResult);
1391        Tcl_DecrRefCount(errorResult);
1392        return TCL_ERROR;
1393    }
1394
1395    /*
1396     * Fix up the widget to react to any new configuration values.
1397     */
1398    Tk_SetBackgroundFromBorder(dvPtr->tkwin, dvPtr->normalBorder);
1399
1400    if (dvPtr->highlightWidth < 0) {
1401        dvPtr->highlightWidth = 0;
1402    }
1403    dvPtr->inset = dvPtr->highlightWidth + dvPtr->borderWidth;
1404
1405    if (dvPtr->tklastfont != dvPtr->tkfont) {
1406        /* the font changed, so re-measure everything */
1407        dvPtr->flags |= FONT_CHANGED;
1408        dvPtr->tklastfont = dvPtr->tkfont;
1409    }
1410
1411    Tk_FreeSavedOptions(&savedOptions);
1412
1413    DiffviewWorldChanged((ClientData)dvPtr);
1414    return TCL_OK;
1415}
1416
1417/*
1418 * ----------------------------------------------------------------------
1419 * DiffviewWorldChanged()
1420 *
1421 * Called by Tk when the world has changed in some way that causes
1422 * all GCs and other things to be recomputed.  Reinitializes the
1423 * widget and gets it ready to draw.
1424 * ----------------------------------------------------------------------
1425 */
1426static void
1427DiffviewWorldChanged(cdata)
1428    ClientData cdata;        /* widget data */
1429{
1430    Diffview *dvPtr = (Diffview*)cdata;
1431    GC gc; XGCValues gcValues;
1432    unsigned long mask;
1433
1434    /* GC for normal widget text */
1435    gcValues.foreground = dvPtr->fgColorPtr->pixel;
1436    gcValues.graphics_exposures = False;
1437    gcValues.font = Tk_FontId(dvPtr->tkfont);
1438    mask = GCForeground | GCFont | GCGraphicsExposures;
1439
1440    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
1441    if (dvPtr->normGC != None) {
1442        Tk_FreeGC(dvPtr->display, dvPtr->normGC);
1443    }
1444    dvPtr->normGC = gc;
1445
1446    /* GC for added diff text */
1447    gcValues.foreground = dvPtr->addFgColorPtr->pixel;
1448    gcValues.graphics_exposures = False;
1449    gcValues.font = Tk_FontId(dvPtr->tkfont);
1450    mask = GCForeground | GCFont | GCGraphicsExposures;
1451
1452    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
1453    if (dvPtr->addFgGC != None) {
1454        Tk_FreeGC(dvPtr->display, dvPtr->addFgGC);
1455    }
1456    dvPtr->addFgGC = gc;
1457
1458    /* GC for deleted diff text */
1459    gcValues.foreground = dvPtr->delFgColorPtr->pixel;
1460    gcValues.graphics_exposures = False;
1461    gcValues.font = Tk_FontId(dvPtr->tkfont);
1462    mask = GCForeground | GCFont | GCGraphicsExposures;
1463
1464    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
1465    if (dvPtr->delFgGC != None) {
1466        Tk_FreeGC(dvPtr->display, dvPtr->delFgGC);
1467    }
1468    dvPtr->delFgGC = gc;
1469
1470    /* GC for changed diff text */
1471    gcValues.foreground = dvPtr->chgFgColorPtr->pixel;
1472    gcValues.graphics_exposures = False;
1473    gcValues.font = Tk_FontId(dvPtr->tkfont);
1474    mask = GCForeground | GCFont | GCGraphicsExposures;
1475
1476    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
1477    if (dvPtr->chgFgGC != None) {
1478        Tk_FreeGC(dvPtr->display, dvPtr->chgFgGC);
1479    }
1480    dvPtr->chgFgGC = gc;
1481
1482    /* this call comes when the font changes -- fix the layout */
1483    dvPtr->flags |= FONT_CHANGED;
1484
1485    /* get ready to redraw */
1486    DiffviewComputeGeometry(dvPtr);
1487    dvPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
1488    EventuallyRedraw(dvPtr);
1489}
1490
1491/*
1492 * ----------------------------------------------------------------------
1493 * EventuallyRedraw()
1494 *
1495 * Arranges for the widget to redraw itself at the next idle point.
1496 * ----------------------------------------------------------------------
1497 */
1498static void
1499EventuallyRedraw(dvPtr)
1500    Diffview *dvPtr;          /* widget data */
1501{
1502    if ((dvPtr->flags & REDRAW_PENDING) || (dvPtr->flags & WIDGET_DELETED)
1503            || !Tk_IsMapped(dvPtr->tkwin)) {
1504        return;  /* no need to redraw */
1505    }
1506
1507    dvPtr->flags |= REDRAW_PENDING;
1508    Tcl_DoWhenIdle(DisplayDiffview, (ClientData)dvPtr);
1509}
1510
1511/*
1512 * ----------------------------------------------------------------------
1513 * DisplayDiffview()
1514 *
1515 * Redraws the widget based on the current view and all data.
1516 * ----------------------------------------------------------------------
1517 */
1518static void
1519DisplayDiffview(cdata)
1520    ClientData cdata;        /* widget data */
1521{
1522    Diffview *dvPtr = (Diffview*)cdata;
1523    Tk_Window tkwin = dvPtr->tkwin;
1524
1525    int i, bnum, bline, x, xw, y, ymid, width;
1526    char *textPtr; int textLen;
1527    Pixmap pixmap;
1528    GC bg, fg;
1529
1530    /* handling redraw now -- no longer pending */
1531    dvPtr->flags &= ~REDRAW_PENDING;
1532
1533    if (dvPtr->flags & WIDGET_DELETED) {
1534        /* widget is being torn down -- bail out! */
1535        return;
1536    }
1537
1538    /* handle any pending layout changes */
1539    DiffviewUpdateLayout(dvPtr);
1540
1541    /*
1542     * Hang onto the widget data until we're done updating scrollbars.
1543     * The -xscrollcommand and -yscrollcommand options may take us into
1544     * code that deletes the widget.
1545     */
1546    Tcl_Preserve((ClientData)dvPtr);
1547
1548    if (dvPtr->flags & UPDATE_V_SCROLLBAR) {
1549        DiffviewUpdateVScrollbar(dvPtr);
1550        if ((dvPtr->flags & WIDGET_DELETED) || !Tk_IsMapped(tkwin)) {
1551            Tcl_Release((ClientData)dvPtr);
1552            return;
1553        }
1554    }
1555    if (dvPtr->flags & UPDATE_H_SCROLLBAR) {
1556        DiffviewUpdateHScrollbar(dvPtr);
1557        if ((dvPtr->flags & WIDGET_DELETED) || !Tk_IsMapped(tkwin)) {
1558            Tcl_Release((ClientData)dvPtr);
1559            return;
1560        }
1561    }
1562    dvPtr->flags &= ~(REDRAW_PENDING|UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR);
1563    Tcl_Release((ClientData)dvPtr);
1564
1565#ifndef TK_NO_DOUBLE_BUFFERING
1566    /*
1567     * Best solution is to draw everything into a temporary pixmap
1568     * and copy that to the screen in one shot.  That avoids flashing.
1569     */
1570    pixmap = Tk_GetPixmap(dvPtr->display, Tk_WindowId(tkwin),
1571            Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
1572#else
1573    pixmap = Tk_WindowId(tkwin);
1574#endif
1575
1576    Tk_Fill3DRectangle(tkwin, pixmap, dvPtr->normalBorder, 0, 0,
1577            Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
1578
1579    /*
1580     * Find the top line in the view in each buffer
1581     */
1582    y = dvPtr->inset + dvPtr->topLine*dvPtr->lineHeight + dvPtr->lineAscent
1583          - dvPtr->yOffset;
1584    x = dvPtr->inset - dvPtr->xOffset;
1585
1586    if (Tk_Width(tkwin) > dvPtr->maxWidth) {
1587        width = Tk_Width(tkwin)+10;
1588    } else {
1589        width = dvPtr->maxWidth+10;
1590    }
1591
1592    for (i=dvPtr->topLine;
1593         i <= dvPtr->btmLine && i < dvPtr->worldview.numLines;
1594         i++) {
1595
1596        /* draw any diff rectangle for this line */
1597        fg = dvPtr->normGC;
1598        bg = None;
1599
1600        switch (dvPtr->worldview.lines[i].style) {
1601            case 'a': {
1602                fg = dvPtr->addFgGC;
1603                bg = Tk_GCForColor(dvPtr->addBgColorPtr, pixmap);
1604                break;
1605            }
1606            case 'd': {
1607                fg = dvPtr->delFgGC;
1608                bg = Tk_GCForColor(dvPtr->delBgColorPtr, pixmap);
1609                break;
1610            }
1611            case 'c': {
1612                fg = dvPtr->chgFgGC;
1613                bg = Tk_GCForColor(dvPtr->chgBgColorPtr, pixmap);
1614                break;
1615            }
1616        }
1617
1618        if (bg != None) {
1619            XFillRectangle(Tk_Display(tkwin), pixmap, bg,
1620                x, y - dvPtr->lineAscent,
1621                (unsigned int)width, (unsigned int)dvPtr->lineHeight);
1622        }
1623
1624        bnum = dvPtr->worldview.lines[i].bnum;
1625        bline = dvPtr->worldview.lines[i].bline;
1626
1627        /* a negative number means "leave the line blank" */
1628        if (bline >= 0) {
1629            textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
1630            textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
1631
1632            /* Draw the actual text of this item */
1633            Tk_DrawChars(dvPtr->display, pixmap, fg, dvPtr->tkfont,
1634                textPtr, textLen, x, y);
1635
1636            /* Draw an overstrike on deleted text */
1637            if (dvPtr->worldview.lines[i].style == 'd' && dvPtr->overStrDel) {
1638                xw = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen) + 5;
1639                ymid = y - dvPtr->lineAscent/2;
1640                XDrawLine(Tk_Display(tkwin), pixmap, fg, 0, ymid, xw, ymid);
1641            }
1642        }
1643
1644        y += dvPtr->lineHeight;
1645    }
1646
1647    /*
1648     * Draw the border on top so that it covers any characters that
1649     * have scrolled off screen.
1650     */
1651    Tk_Draw3DRectangle(tkwin, pixmap, dvPtr->normalBorder,
1652            dvPtr->highlightWidth, dvPtr->highlightWidth,
1653            Tk_Width(tkwin) - 2*dvPtr->highlightWidth,
1654            Tk_Height(tkwin) - 2*dvPtr->highlightWidth,
1655            dvPtr->borderWidth, dvPtr->relief);
1656
1657    if (dvPtr->highlightWidth > 0) {
1658        XColor *color; GC gc;
1659
1660        color = (dvPtr->flags & GOT_FOCUS)
1661            ? dvPtr->highlightColor : dvPtr->highlightBgColor;
1662        gc = Tk_GCForColor(color, pixmap);
1663        Tk_DrawFocusHighlight(dvPtr->tkwin, gc, dvPtr->highlightWidth, pixmap);
1664    }
1665
1666#ifndef TK_NO_DOUBLE_BUFFERING
1667    XCopyArea(dvPtr->display, pixmap, Tk_WindowId(tkwin),
1668            dvPtr->normGC, 0, 0, (unsigned) Tk_Width(tkwin),
1669            (unsigned) Tk_Height(tkwin), 0, 0);
1670    Tk_FreePixmap(dvPtr->display, pixmap);
1671#endif
1672}
1673
1674/*
1675 * ----------------------------------------------------------------------
1676 * DiffviewComputeGeometry()
1677 *
1678 * Recomputes the overall dimensions of the widget when the overall
1679 * size or the borderwidth is changed.  Registers a new size for the
1680 * widget.
1681 * ----------------------------------------------------------------------
1682 */
1683static void
1684DiffviewComputeGeometry(dvPtr)
1685    Diffview *dvPtr;          /* widget data */
1686{
1687    int pixelWidth, pixelHeight;
1688
1689    dvPtr->xScrollUnit = Tk_TextWidth(dvPtr->tkfont, "0", 1);
1690    if (dvPtr->xScrollUnit == 0) {
1691        dvPtr->xScrollUnit = 1;
1692    }
1693
1694    pixelWidth = dvPtr->width + 2*dvPtr->inset;
1695    pixelHeight = dvPtr->height + 2*dvPtr->inset;
1696    Tk_GeometryRequest(dvPtr->tkwin, pixelWidth, pixelHeight);
1697    Tk_SetInternalBorder(dvPtr->tkwin, dvPtr->inset);
1698}
1699
1700/*
1701 * ----------------------------------------------------------------------
1702 * DiffviewEventProc()
1703 *
1704 * Catches important events on the widget and causes it to redraw
1705 * itself and react to focus changes.
1706 * ----------------------------------------------------------------------
1707 */
1708static void
1709DiffviewEventProc(cdata, eventPtr)
1710    ClientData cdata;        /* widget data */
1711    XEvent *eventPtr;        /* event information coming from X11 */
1712{
1713    Diffview *dvPtr = (Diffview*)cdata;
1714   
1715    if (eventPtr->type == Expose) {
1716        EventuallyRedraw(dvPtr);
1717    }
1718    else if (eventPtr->type == DestroyNotify) {
1719        if (!(dvPtr->flags & WIDGET_DELETED)) {
1720            dvPtr->flags |= WIDGET_DELETED;
1721            Tcl_DeleteCommandFromToken(dvPtr->interp, dvPtr->widgetCmd);
1722            if (dvPtr->flags & REDRAW_PENDING) {
1723                Tcl_CancelIdleCall(DisplayDiffview, cdata);
1724            }
1725            Tcl_EventuallyFree(cdata, DestroyDiffview);
1726        }
1727    }
1728    else if (eventPtr->type == ConfigureNotify) {
1729        dvPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
1730        ChangeDiffviewView(dvPtr, dvPtr->topLine);
1731        EventuallyRedraw(dvPtr);
1732    }
1733    else if (eventPtr->type == FocusIn) {
1734        if (eventPtr->xfocus.detail != NotifyInferior) {
1735            dvPtr->flags |= GOT_FOCUS;
1736            EventuallyRedraw(dvPtr);
1737        }
1738    }
1739    else if (eventPtr->type == FocusOut) {
1740        if (eventPtr->xfocus.detail != NotifyInferior) {
1741            dvPtr->flags &= ~GOT_FOCUS;
1742            EventuallyRedraw(dvPtr);
1743        }
1744    }
1745}
1746
1747/*
1748 * ----------------------------------------------------------------------
1749 * DiffviewCmdDeletedProc --
1750 *
1751 * Invoked whenever the command associated with the widget is deleted.
1752 * If the widget is not already being destroyed, this starts the process.
1753 * ----------------------------------------------------------------------
1754 */
1755static void
1756DiffviewCmdDeletedProc(cdata)
1757    ClientData cdata;        /* widget data */
1758{
1759    Diffview *dvPtr = (Diffview*)cdata;
1760
1761    /*
1762     * This gets invoked whenever the command is deleted.  If we haven't
1763     * started destroying the widget yet, then do it now to clean up.
1764     */
1765    if (!(dvPtr->flags & WIDGET_DELETED)) {
1766        Tk_DestroyWindow(dvPtr->tkwin);
1767    }
1768}
1769
1770/*
1771 * ----------------------------------------------------------------------
1772 * ChangeDiffviewView()
1773 *
1774 * Changes the y-view of the widget for scrolling operations.  Sets the
1775 * given line to the top of the view.
1776 * ----------------------------------------------------------------------
1777 */
1778static void
1779ChangeDiffviewView(dvPtr, lnum)
1780    Diffview *dvPtr;          /* widget data */
1781    int lnum;                 /* this line should appear at the top */
1782{
1783    int maxLines, ySize, yLines;
1784
1785    DiffviewUpdateLayout(dvPtr);
1786    ySize = Tk_Height(dvPtr->tkwin) - 2*dvPtr->inset;
1787    yLines = ySize/dvPtr->lineHeight;
1788    maxLines = dvPtr->maxHeight/dvPtr->lineHeight;
1789
1790    if (lnum >= (maxLines - yLines)) {
1791        lnum = maxLines - yLines;
1792    }
1793    if (lnum < 0) {
1794        lnum = 0;
1795    }
1796    if (dvPtr->topLine != lnum) {
1797        dvPtr->topLine = lnum;
1798        dvPtr->yOffset = lnum * dvPtr->lineHeight;
1799        EventuallyRedraw(dvPtr);
1800        dvPtr->flags |= UPDATE_V_SCROLLBAR;
1801    }
1802}
1803
1804/*
1805 * ----------------------------------------------------------------------
1806 * DiffviewScanTo()
1807 *
1808 * Implements the "scan dragto" operation on the widget.  Given a
1809 * starting point from the "scan mark" operation, this adjusts the
1810 * view to the given location.
1811 * ----------------------------------------------------------------------
1812 */
1813static void
1814DiffviewScanTo(dvPtr, x, y)
1815    Diffview *dvPtr;          /* widget data */
1816    int x;                    /* x-coord for drag-to location */
1817    int y;                    /* y-coord for drag-to location */
1818{
1819    int newTopLine, newOffset, maxLine, maxOffset;
1820
1821    /*
1822     * Compute the top line for the display by amplifying the difference
1823     * between the current "drag" point and the original "start" point.
1824     */
1825    maxLine = dvPtr->maxHeight/dvPtr->lineHeight - dvPtr->fullLines;
1826
1827    newTopLine = dvPtr->scanMarkYStart
1828            - (10*(y - dvPtr->scanMarkY))/dvPtr->lineHeight;
1829
1830    if (newTopLine > maxLine) {
1831        newTopLine = dvPtr->scanMarkYStart = maxLine;
1832        dvPtr->scanMarkY = y;
1833    }
1834    else if (newTopLine < 0) {
1835        newTopLine = dvPtr->scanMarkYStart = 0;
1836        dvPtr->scanMarkY = y;
1837    }
1838    ChangeDiffviewView(dvPtr, newTopLine);
1839
1840    /*
1841     * Compute the left edge for the display by amplifying the difference
1842     * between the current "drag" point and the original "start" point.
1843     */
1844    maxOffset = dvPtr->maxWidth - (Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset);
1845    newOffset = dvPtr->scanMarkXStart - (10*(x - dvPtr->scanMarkX));
1846    if (newOffset > maxOffset) {
1847        newOffset = dvPtr->scanMarkXStart = maxOffset;
1848        dvPtr->scanMarkX = x;
1849    } else if (newOffset < 0) {
1850        newOffset = dvPtr->scanMarkXStart = 0;
1851        dvPtr->scanMarkX = x;
1852    }
1853    newOffset -= newOffset % dvPtr->xScrollUnit;
1854
1855    if (newOffset != dvPtr->xOffset) {
1856        dvPtr->xOffset = newOffset;
1857        dvPtr->flags |= UPDATE_H_SCROLLBAR;
1858        EventuallyRedraw(dvPtr);
1859    }
1860}
1861
1862/*
1863 * ----------------------------------------------------------------------
1864 * DiffviewUpdateLayout()
1865 *
1866 * Called whenever the widget is about to access layout information
1867 * to make sure that the latest info is in place and up-to-date.
1868 * Computes the line start/end for each line in each buffer, then
1869 * computes the diffs and figures out how many lines to show.
1870 * ----------------------------------------------------------------------
1871 */
1872static void
1873DiffviewUpdateLayout(dvPtr)
1874    Diffview *dvPtr;          /* widget data */
1875{
1876    int changes = 0;   /* no layout changes yet */
1877    DiffviewBuffer* bufferPtr;
1878    DiffviewDiffOp *currOp;
1879    DiffviewLayoutLine line;
1880    int i, bnum, bline, pixelWidth, maxWidth, ySize;
1881    int numLines1, numLines2, lnum1, lnum2, dnum, lastdnum, pastDiff;
1882    char *textPtr, *textPtr2; int textLen;
1883    Tk_FontMetrics fm;
1884
1885    /* if the font changed, then re-measure everything */
1886    changes = ((dvPtr->flags & FONT_CHANGED) != 0);
1887
1888    /* fill in line limits for any new data */
1889    for (bnum=0; bnum < 2; bnum++) {
1890        bufferPtr = &dvPtr->buffer[bnum];
1891
1892        if (bufferPtr->textObj != NULL && bufferPtr->lineLimits == NULL) {
1893            textPtr = Tcl_GetStringFromObj(bufferPtr->textObj, &textLen);
1894            bufferPtr->lineLimits = DiffviewLinesCreate(textPtr, textLen);
1895            changes = 1;
1896        }
1897    }
1898
1899    if (changes) {
1900        /* recompute the diffs between the buffers */
1901        if (dvPtr->diffsPtr) {
1902            DiffviewDiffsFree(dvPtr->diffsPtr);
1903            dvPtr->diffsPtr = NULL;
1904        }
1905
1906        if (dvPtr->buffer[0].textObj && dvPtr->buffer[1].textObj) {
1907            textPtr = Tcl_GetStringFromObj(dvPtr->buffer[0].textObj, &textLen);
1908            textPtr2 = Tcl_GetStringFromObj(dvPtr->buffer[1].textObj, &textLen);
1909
1910            dvPtr->diffsPtr = DiffviewDiffsCreate(
1911                textPtr, dvPtr->buffer[0].lineLimits,
1912                textPtr2, dvPtr->buffer[1].lineLimits);
1913        }
1914
1915        /*
1916         * Compute the layout of all lines according to the current
1917         * view mode.  Each line in the world view is stored in the
1918         * array dvPtr->worldview.lines.  Each line has a style (color
1919         * for normal, add, delete, etc.) and an indication of which
1920         * buffer it comes from.
1921         */
1922        DiffviewLayoutClear(&dvPtr->worldview);
1923
1924        /*
1925         * March through the lines and figure out the source and style
1926         * of each line based on the diffs:
1927         *   'n' = normal (common) line
1928         *   'a' = draw with the "added" style
1929         *   'd' = draw with the "deleted" style
1930         *   'c' = draw with the "changed" style
1931         */
1932        numLines1 = (dvPtr->buffer[0].lineLimits)
1933                       ? dvPtr->buffer[0].lineLimits->numLines : 0;
1934        numLines2 = (dvPtr->buffer[1].lineLimits)
1935                       ? dvPtr->buffer[1].lineLimits->numLines : 0;
1936
1937        dnum = 0;  /* current difference in diffsPtr */
1938        lnum1 = lnum2 = 0;
1939
1940        while (lnum1 < numLines1 || lnum2 < numLines2) {
1941            /* assume it's a normal-looking line (buffers are the same) */
1942            line.style = 'n';
1943            line.bnum = 0;
1944            line.bline = lnum1;
1945            line.diffNum = -1;
1946
1947            /* is there a diff that contains this line? */
1948            if (dvPtr->diffsPtr && dnum < dvPtr->diffsPtr->numDiffs) {
1949              currOp = &dvPtr->diffsPtr->ops[dnum];
1950
1951              if ( (lnum1 >= currOp->fromIndex1
1952                     && lnum1 <= currOp->toIndex1)
1953                || (lnum2 >= currOp->fromIndex2
1954                     && lnum2 <= currOp->toIndex2) ) {
1955
1956                line.diffNum = dnum;  /* line is part of this diff */
1957
1958                switch (currOp->op) {
1959                    case 'c': {
1960                        if (dvPtr->layout == LAYOUT_INLINE) {
1961                            if (dvPtr->diffdir == DIFF_1TO2) {
1962                                /* show the buffer #1 lines first, then #2 */
1963                                if (lnum1 <= currOp->toIndex1) {
1964                                    line.style = 'd';
1965                                    line.bnum = 0;
1966                                    line.bline = lnum1++;
1967                                } else {
1968                                    line.style = 'a';
1969                                    line.bnum = 1;
1970                                    line.bline = lnum2++;
1971                                }
1972                            } else {
1973                                /* reverse -- show #2 lines first, then #1 */
1974                                if (lnum2 <= currOp->toIndex2) {
1975                                    line.style = 'd';
1976                                    line.bnum = 1;
1977                                    line.bline = lnum2++;
1978                                } else {
1979                                    line.style = 'a';
1980                                    line.bnum = 0;
1981                                    line.bline = lnum1++;
1982                                }
1983                            }
1984                        } else {
1985                            if (dvPtr->diffdir == DIFF_1TO2) {
1986                                /* show final lines in buf #2 as "changed" */
1987                                line.style = 'c';
1988                                line.bnum = 1;
1989                                line.bline = lnum2++;
1990                                lnum1 = currOp->toIndex1+1;
1991                            } else {
1992                                /* show final lines in buf #1 as "changed" */
1993                                line.style = 'c';
1994                                line.bnum = 0;
1995                                line.bline = lnum1++;
1996                                lnum2 = currOp->toIndex2+1;
1997                            }
1998                        }
1999                        break;
2000                    }
2001                    case 'a': {
2002                        if (dvPtr->diffdir == DIFF_1TO2) {
2003                            /* show lines in buffer #2 as 'added' */
2004                            line.style = 'a';
2005                            line.bnum = 1;
2006                            line.bline = lnum2++;
2007                            lnum1 = currOp->toIndex1;
2008                        } else {
2009                            /* reverse diff -- like we're deleting lines */
2010                            if (dvPtr->layout == LAYOUT_INLINE) {
2011                                line.style = 'd';
2012                                line.bnum = 1;
2013                                line.bline = lnum2++;
2014                                lnum1 = currOp->toIndex1;
2015                            } else {
2016                                /* show lines from buffer #2 as empty */
2017                                line.style = 'd';
2018                                line.bnum = 0;
2019                                line.bline = -1;
2020                                lnum2++;
2021                                lnum1 = currOp->toIndex1;
2022                            }
2023                        }
2024                        break;
2025                    }
2026                    case 'd': {
2027                        if (dvPtr->diffdir == DIFF_1TO2) {
2028                            if (dvPtr->layout == LAYOUT_INLINE) {
2029                                line.style = 'd';
2030                                line.bnum = 0;
2031                                line.bline = lnum1++;
2032                                lnum2 = currOp->toIndex2;
2033                            } else {
2034                                /* show lines from buffer #2 as empty */
2035                                line.style = 'd';
2036                                line.bnum = 0;
2037                                line.bline = -1;
2038                                lnum1++;
2039                                lnum2 = currOp->toIndex2;
2040                            }
2041                        } else {
2042                            /* reverse diff -- like we're adding lines */
2043                            line.style = 'a';
2044                            line.bnum = 0;
2045                            line.bline = lnum1++;
2046                            lnum2 = currOp->toIndex2;
2047                        }
2048                        break;
2049                    }
2050                    default: {
2051                        Tcl_Panic("bad diff type '%c' found in layout",
2052                            currOp->op);
2053                        break;
2054                    }
2055                }
2056              } else {
2057                /* normal line -- keep moving forward */
2058                lnum1++; lnum2++;
2059              }
2060
2061              /* have we reached the end of the diff? then move on */
2062              pastDiff = 0;
2063              switch (currOp->op) {
2064                  case 'c':
2065                      pastDiff = (lnum1 > currOp->toIndex1
2066                               && lnum2 > currOp->toIndex2);
2067                      break;
2068                  case 'a':
2069                      pastDiff = (lnum2 > currOp->toIndex2);
2070                      break;
2071                  case 'd':
2072                      pastDiff = (lnum1 > currOp->toIndex1);
2073                      break;
2074              }
2075              if (pastDiff) {
2076                  dnum++;
2077              }
2078            } else {
2079                /* no more diffs -- keep moving forward */
2080                lnum1++; lnum2++;
2081            }
2082
2083            /* add this new line to the layout */
2084            DiffviewLayoutAdd(&dvPtr->worldview, &line);
2085        }
2086
2087        /*
2088         * Figure out where the diffs are located, and put that info
2089         * back into the diffs.  This makes it easy later to refer to
2090         * diff "#3" and understand what lines we're talking about.
2091         */
2092        lastdnum = -1;
2093        for (i=0; i < dvPtr->worldview.numLines; i++) {
2094            dnum = dvPtr->worldview.lines[i].diffNum;
2095            if (dnum != lastdnum) {
2096                if (lastdnum < 0) {
2097                    /* leading edge -- catch the "from" line */
2098                    lnum1 = i;
2099                } else {
2100                    /* trailing edge -- save diff info */
2101                    dvPtr->diffsPtr->ops[lastdnum].fromWorld = lnum1;
2102                    dvPtr->diffsPtr->ops[lastdnum].toWorld = i-1;
2103                }
2104            }
2105            lastdnum = dnum;
2106        }
2107        if (lastdnum >= 0) {
2108            dvPtr->diffsPtr->ops[lastdnum].fromWorld = lnum1;
2109            dvPtr->diffsPtr->ops[lastdnum].toWorld = i-1;
2110        }
2111
2112        /* compute overall text width for all lines */
2113        maxWidth = 0;
2114        for (i=0; i < dvPtr->worldview.numLines; i++) {
2115            bline = dvPtr->worldview.lines[i].bline;
2116            if (bline >= 0) {
2117                bnum = dvPtr->worldview.lines[i].bnum;
2118                bufferPtr = &dvPtr->buffer[bnum];
2119
2120                if (bufferPtr->lineLimits) {
2121                    textPtr = bufferPtr->lineLimits->startPtr[bline];
2122                    textLen = bufferPtr->lineLimits->lenPtr[bline];
2123                    pixelWidth = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
2124
2125                    if (pixelWidth > maxWidth) {
2126                        maxWidth = pixelWidth;
2127                    }
2128                }
2129            }
2130        }
2131        dvPtr->maxWidth = maxWidth;
2132
2133        /* compute overall height of the widget */
2134        Tk_GetFontMetrics(dvPtr->tkfont, &fm);
2135        dvPtr->lineHeight = fm.linespace;
2136        dvPtr->lineAscent = fm.ascent;
2137        dvPtr->maxHeight = dvPtr->worldview.numLines * fm.linespace;
2138
2139        /* figure out the limits of the current view */
2140        ySize = Tk_Height(dvPtr->tkwin) - 2*dvPtr->inset;
2141        dvPtr->topLine = dvPtr->yOffset/fm.linespace;
2142        dvPtr->btmLine = (dvPtr->yOffset + ySize)/fm.linespace + 1;
2143        dvPtr->fullLines = ySize/fm.linespace;
2144
2145        dvPtr->flags &= ~FONT_CHANGED;
2146    }
2147}
2148
2149/*
2150 * ----------------------------------------------------------------------
2151 * DiffviewUpdateVScrollbar()
2152 *
2153 * Used to invoke the -yscrollcommand whenever the y-view has changed.
2154 * This updates the scrollbar so that it shows the proper bubble.
2155 * If there is no -yscrollcommand option, this does nothing.
2156 * ----------------------------------------------------------------------
2157 */
2158static void
2159DiffviewUpdateVScrollbar(dvPtr)
2160    Diffview *dvPtr;          /* widget data */
2161{
2162    char string[TCL_DOUBLE_SPACE*2 + 2];
2163    double first, last;
2164    int result, ySize;
2165
2166    /* make sure the layout info is up to date */
2167    DiffviewUpdateLayout(dvPtr);
2168
2169    /* update the limits of the current view */
2170    ySize = Tk_Height(dvPtr->tkwin) - 2*dvPtr->inset;
2171    if (dvPtr->lineHeight > 0) {
2172        dvPtr->topLine = dvPtr->yOffset/dvPtr->lineHeight;
2173        dvPtr->btmLine = (dvPtr->yOffset + ySize)/dvPtr->lineHeight + 1;
2174        dvPtr->fullLines = ySize/dvPtr->lineHeight;
2175    }
2176
2177    /* if there's no scroll command, then there's nothing left to do */
2178    if (dvPtr->yScrollCmd == NULL) {
2179        return;
2180    }
2181
2182    if (dvPtr->maxHeight == 0) {
2183        first = 0.0;
2184        last = 1.0;
2185    } else {
2186        first = dvPtr->yOffset/((double)dvPtr->maxHeight);
2187        last = (dvPtr->yOffset + ySize)/((double)dvPtr->maxHeight);
2188        if (last > 1.0) {
2189            last = 1.0;
2190        }
2191    }
2192    sprintf(string, " %g %g", first, last);
2193
2194    /* preserve/release the interp, in case it gets destroyed during the call */
2195    Tcl_Preserve((ClientData)dvPtr->interp);
2196
2197    result = Tcl_VarEval(dvPtr->interp, dvPtr->yScrollCmd, string,
2198            (char*)NULL);
2199    if (result != TCL_OK) {
2200        Tcl_AddErrorInfo(dvPtr->interp,
2201                "\n    (vertical scrolling command executed by diffview)");
2202        Tcl_BackgroundError(dvPtr->interp);
2203    }
2204
2205    Tcl_Release((ClientData)dvPtr->interp);
2206}
2207
2208/*
2209 * ----------------------------------------------------------------------
2210 * DiffviewUpdateHScrollbar()
2211 *
2212 * Used to invoke the -xscrollcommand whenever the x-view has changed.
2213 * This updates the scrollbar so that it shows the proper bubble.
2214 * If there is no -xscrollcommand option, this does nothing.
2215 * ----------------------------------------------------------------------
2216 */
2217static void
2218DiffviewUpdateHScrollbar(dvPtr)
2219    Diffview *dvPtr;          /* widget data */
2220{
2221    char string[TCL_DOUBLE_SPACE*2 + 3];
2222    int result, viewWidth;
2223    double first, last;
2224
2225    /* make sure the layout info is up to date */
2226    DiffviewUpdateLayout(dvPtr);
2227
2228    /* if there's no scroll command, then there's nothing left to do */
2229    if (dvPtr->xScrollCmd == NULL) {
2230        return;
2231    }
2232
2233    viewWidth = Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset;
2234    if (dvPtr->maxWidth == 0) {
2235        first = 0;
2236        last = 1.0;
2237    } else {
2238        first = dvPtr->xOffset/((double)dvPtr->maxWidth);
2239        last = (dvPtr->xOffset + viewWidth)/((double) dvPtr->maxWidth);
2240        if (last > 1.0) {
2241            last = 1.0;
2242        }
2243    }
2244    sprintf(string, " %g %g", first, last);
2245
2246    /* preserve/release the interp, in case it gets destroyed during the call */
2247    Tcl_Preserve((ClientData)dvPtr->interp);
2248
2249    result = Tcl_VarEval(dvPtr->interp, dvPtr->xScrollCmd, string,
2250            (char*)NULL);
2251    if (result != TCL_OK) {
2252        Tcl_AddErrorInfo(dvPtr->interp,
2253                "\n    (horizontal scrolling command executed by diffview)");
2254        Tcl_BackgroundError(dvPtr->interp);
2255    }
2256
2257    Tcl_Release((ClientData)dvPtr->interp);
2258}
2259
2260/*
2261 * ----------------------------------------------------------------------
2262 * DiffviewLinesCreate()
2263 *
2264 * Used to breakup a Tcl string object into the indices for the start
2265 * and end of each line.  This is the first step in the computation
2266 * of diffs and the world view of the widget.
2267 *
2268 * Returns a pointer to a description of the line geometry, which should
2269 * be freed by calling DiffviewLinesFree() when it is no longer needed.
2270 * ----------------------------------------------------------------------
2271 */
2272static DiffviewLines*
2273DiffviewLinesCreate(textPtr, textLen)
2274    char *textPtr;                  /* text string being broken up */
2275    int textLen;                    /* length of the string */
2276{
2277    int numLines = 0;
2278    DiffviewLines *lineLimitsPtr;
2279    char **newStarts;
2280    int *newLens;
2281    unsigned int len;
2282
2283    lineLimitsPtr = (DiffviewLines*)ckalloc(sizeof(DiffviewLines));
2284    lineLimitsPtr->maxLines = 0;
2285    lineLimitsPtr->startPtr = NULL;
2286    lineLimitsPtr->lenPtr = NULL;
2287
2288    while (textLen > 0) {
2289        /*
2290         * If we're out of space, double the size and copy existing
2291         * info over.
2292         */
2293        if (numLines >= lineLimitsPtr->maxLines) {
2294            if (lineLimitsPtr->maxLines == 0) {
2295                lineLimitsPtr->maxLines = 100;
2296            } else {
2297                lineLimitsPtr->maxLines *= 2;
2298            }
2299
2300            /* resize the start point array */
2301            newStarts = (char**)ckalloc(lineLimitsPtr->maxLines*sizeof(char*));
2302            if (lineLimitsPtr->startPtr) {
2303                memcpy((VOID*)newStarts, (VOID*)lineLimitsPtr->startPtr,
2304                    numLines*sizeof(char*));
2305                ckfree((char*)lineLimitsPtr->startPtr);
2306            }
2307            lineLimitsPtr->startPtr = newStarts;
2308
2309            /* resize the end point array */
2310            newLens = (int*)ckalloc(lineLimitsPtr->maxLines*sizeof(int));
2311            if (lineLimitsPtr->lenPtr) {
2312                memcpy((VOID*)newLens, (VOID*)lineLimitsPtr->lenPtr,
2313                    numLines*sizeof(int));
2314                ckfree((char*)lineLimitsPtr->lenPtr);
2315            }
2316            lineLimitsPtr->lenPtr = newLens;
2317        }
2318
2319        /* mark the start of this line, then search for the end */
2320        lineLimitsPtr->startPtr[numLines] = textPtr;
2321        lineLimitsPtr->lenPtr[numLines] = 0;
2322
2323        while (*textPtr != '\n' && textLen > 0) {
2324            textPtr++;  textLen--;
2325        }
2326        if (textPtr > lineLimitsPtr->startPtr[numLines]) {
2327            len = textPtr - lineLimitsPtr->startPtr[numLines];
2328            lineLimitsPtr->lenPtr[numLines] = len;
2329        }
2330        numLines++;
2331
2332        /* skip over the newline and start with next line */
2333        if (*textPtr == '\n') {
2334            textPtr++;  textLen--;
2335        }
2336    }
2337    lineLimitsPtr->numLines = numLines;
2338
2339    return lineLimitsPtr;
2340}
2341
2342/*
2343 * ----------------------------------------------------------------------
2344 * DiffviewLinesFree()
2345 *
2346 * Used to free up the data structure created by DiffviewLinesCreate().
2347 * ----------------------------------------------------------------------
2348 */
2349static void
2350DiffviewLinesFree(lineLimitsPtr)
2351    DiffviewLines *lineLimitsPtr;   /* data structure being freed */
2352{
2353    if (lineLimitsPtr) {
2354        if (lineLimitsPtr->startPtr) {
2355            ckfree((char*)lineLimitsPtr->startPtr);
2356        }
2357        if (lineLimitsPtr->lenPtr) {
2358            ckfree((char*)lineLimitsPtr->lenPtr);
2359        }
2360        ckfree((char*)lineLimitsPtr);
2361    }
2362}
2363
2364/*
2365 * ----------------------------------------------------------------------
2366 * DiffviewDiffsCreate()
2367 *
2368 * Considers two strings textPtr1 and textPtr2 (divided into segments
2369 * according to limsPtr1 and limsPtr2), and computes the longest common
2370 * subsequences between the two strings.  This is the first step in
2371 * computing the differences between the two strings.
2372 *
2373 * Returns a data structure that contains a series of "diff" operations.
2374 * This should be freed when it is no longer needed by calling
2375 * DiffviewDiffsFree().
2376 *
2377 *   REFERENCE:
2378 *   J. W. Hunt and M. D. McIlroy, "An algorithm for differential
2379 *   file comparison," Comp. Sci. Tech. Rep. #41, Bell Telephone
2380 *   Laboratories (1976). Available on the Web at the second
2381 *   author's personal site: http://www.cs.dartmouth.edu/~doug/
2382 *
2383 * ----------------------------------------------------------------------
2384 */
2385static DiffviewDiffs*
2386DiffviewDiffsCreate(textPtr1, limsPtr1, textPtr2, limsPtr2)
2387    char *textPtr1;           /* text from buffer #1 */
2388    DiffviewLines *limsPtr1;  /* limits of individual strings in textPtr1 */
2389    char *textPtr2;           /* text from buffer #2 */
2390    DiffviewLines *limsPtr2;  /* limits of individual strings in textPtr2 */
2391{
2392    DiffviewDiffs *diffPtr = NULL;
2393
2394    Tcl_HashTable eqv;
2395    Tcl_HashSearch iter;
2396    Tcl_HashEntry *entryPtr;
2397    Tcl_DString buffer;
2398    DiffviewSubseq *K, *newK, newCandidate;
2399    int Kmax; int Klen;
2400    Tcl_Obj *listPtr, **objv;
2401    int i, j, candidateIdx, subseqLen, longestMatch;
2402    int max, min, mid, midval, sLen, del;
2403    int len, created, o, objc, index1, *lcsIndex1, index2, *lcsIndex2;
2404    char *key;
2405
2406    newCandidate.index1 = -1;           /* Suppress compiler warning. */
2407    newCandidate.index2 = -1;
2408    newCandidate.next = -1;
2409
2410    /*
2411     * Build a set of equivalence classes.  Scan through all of
2412     * buffer #2 and map each string to a list of indices for the
2413     * lines that have that string.
2414     */
2415    Tcl_DStringInit(&buffer);
2416    Tcl_InitHashTable(&eqv, TCL_STRING_KEYS);
2417    for (i=0; i < limsPtr2->numLines; i++) {
2418        len = limsPtr2->lenPtr[i];
2419        Tcl_DStringSetLength(&buffer, len);
2420        key = Tcl_DStringValue(&buffer);
2421        memcpy((VOID*)key, (VOID*)limsPtr2->startPtr[i], len);
2422
2423        entryPtr = Tcl_CreateHashEntry(&eqv, key, &created);
2424        if (created) {
2425            listPtr = Tcl_NewListObj(0, (Tcl_Obj**)NULL);
2426            Tcl_IncrRefCount(listPtr);
2427            Tcl_SetHashValue(entryPtr, (ClientData)listPtr);
2428        }
2429
2430        listPtr = (Tcl_Obj*)Tcl_GetHashValue(entryPtr);
2431        Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr, Tcl_NewIntObj(i));
2432    }
2433
2434    /*
2435     * Build a list K that holds the descriptions of the common
2436     * subsequences.  At first, there is one common subsequence of
2437     * length 0 with a fence that includes line -1 of both files.
2438     */
2439    Kmax = 10;
2440    K = (DiffviewSubseq*)ckalloc(Kmax*sizeof(DiffviewSubseq));
2441    K[0].index1 = -1;
2442    K[0].index2 = -1;
2443    K[0].next = -1;
2444    K[1].index1 = limsPtr1->numLines;
2445    K[1].index2 = limsPtr2->numLines;
2446    K[1].next = -1;
2447    Klen = 2;
2448    longestMatch = 0;
2449
2450    /*
2451     * Step through the first buffer line by line.
2452     */
2453    for (i=0; i < limsPtr1->numLines; i++) {
2454        len = limsPtr1->lenPtr[i];
2455        Tcl_DStringSetLength(&buffer, len);
2456        key = Tcl_DStringValue(&buffer);
2457        memcpy((VOID*)key, (VOID*)limsPtr1->startPtr[i], len);
2458
2459        /* look at each possible line j in second buffer */
2460        entryPtr = Tcl_FindHashEntry(&eqv, key);
2461        if (entryPtr) {
2462            subseqLen = 0;
2463            candidateIdx = 0;
2464
2465            listPtr = (Tcl_Obj*)Tcl_GetHashValue(entryPtr);
2466            Tcl_ListObjGetElements((Tcl_Interp*)NULL, listPtr, &objc, &objv);
2467            for (o=0; o < objc; o++) {
2468                Tcl_GetIntFromObj((Tcl_Interp*)NULL, objv[o], &j);
2469
2470                /*
2471                 * Binary search to find a candidate common subsequence.
2472                 * This match may get appended to that.
2473                 */
2474                max  = longestMatch;
2475                min  = subseqLen;
2476                mid  = subseqLen;
2477                sLen = longestMatch + 1;
2478                while (max >= min) {
2479                    mid = (max+min)/2;
2480                    midval = K[mid].index2;
2481                    if (j == midval) {
2482                        break;
2483                    } else if (j < midval) {
2484                        max = mid-1;
2485                    } else {
2486                        sLen = mid;
2487                        min = mid+1;
2488                    }
2489                }
2490
2491                /* no good candidate? then go to the next match point */
2492                if (j == K[mid].index2 || sLen > longestMatch) {
2493                    continue;
2494                }
2495
2496                /*
2497                 * sLen = sequence length of the longest sequence that
2498                 *        this match point can be appended to
2499                 *
2500                 * Make a new candidate match and store the old on in K.
2501                 * Set subseqLen to the length of the new candidate match.
2502                 */
2503                if (subseqLen >= 0) {
2504                    if (candidateIdx >= 0) {
2505                        K[subseqLen].index1 = K[candidateIdx].index1;
2506                        K[subseqLen].index2 = K[candidateIdx].index2;
2507                        K[subseqLen].next   = K[candidateIdx].next;
2508                    } else {
2509                        K[subseqLen].index1 = newCandidate.index1;
2510                        K[subseqLen].index2 = newCandidate.index2;
2511                        K[subseqLen].next   = newCandidate.next;
2512                    }
2513                }
2514                newCandidate.index1 = i;
2515                newCandidate.index2 = j;
2516                newCandidate.next = sLen;
2517                candidateIdx = -1;  /* use newCandidate info */
2518                subseqLen = sLen + 1;
2519
2520                /*
2521                 * If we've extended the length of the longest match,
2522                 * we don't need to keep checking candidate lines.
2523                 * We're done.  Move the fence.
2524                 */
2525                if (sLen >= longestMatch) {
2526                    if (Klen >= Kmax) {
2527                        Kmax *= 2;
2528                        newK = (DiffviewSubseq*)ckalloc(Kmax*sizeof(DiffviewSubseq));
2529                        memcpy((VOID*)newK, (VOID*)K, Klen*sizeof(DiffviewSubseq));
2530                        ckfree((char*)K);
2531                        K = newK;
2532                    }
2533                    K[Klen].index1 = K[Klen-1].index1;
2534                    K[Klen].index2 = K[Klen-1].index2;
2535                    K[Klen].next   = K[Klen-1].next;
2536                    Klen++;
2537
2538                    longestMatch++;
2539                    break;
2540                }
2541            }
2542
2543            /* put the last candidate into the array */
2544            if (candidateIdx >= 0) {
2545                K[subseqLen].index1 = K[candidateIdx].index1;
2546                K[subseqLen].index2 = K[candidateIdx].index2;
2547                K[subseqLen].next   = K[candidateIdx].next;
2548            } else {
2549                K[subseqLen].index1 = newCandidate.index1;
2550                K[subseqLen].index2 = newCandidate.index2;
2551                K[subseqLen].next   = newCandidate.next;
2552            }
2553        }
2554    }
2555
2556    /*
2557     * Translate the resulting info into a list of common subseq indices.
2558     */
2559    lcsIndex1 = (int*)ckalloc(longestMatch*sizeof(int));
2560    memset((void*)lcsIndex1, 0, (longestMatch*sizeof(int)));
2561    lcsIndex2 = (int*)ckalloc(longestMatch*sizeof(int));
2562    memset((void*)lcsIndex2, 0, (longestMatch*sizeof(int)));
2563    len = longestMatch;
2564
2565    candidateIdx = longestMatch;
2566    while (K[candidateIdx].index1 >= 0) {
2567        if (longestMatch-- < 0) {
2568            Tcl_Panic("internal mismatch in diff algorithm");
2569        }
2570        lcsIndex1[longestMatch] = K[candidateIdx].index1;
2571        lcsIndex2[longestMatch] = K[candidateIdx].index2;
2572        candidateIdx = K[candidateIdx].next;
2573    }
2574
2575    /*
2576     * Now, march through all lines in both buffers and convert the
2577     * lcs indices into a sequence of diff-style operations.
2578     */
2579    diffPtr = (DiffviewDiffs*)ckalloc(sizeof(DiffviewDiffs));
2580    diffPtr->maxDiffs = 0;
2581    diffPtr->numDiffs = 0;
2582    diffPtr->ops = NULL;
2583
2584    i = j = 0;
2585    for (o=0; o < len; o++) {
2586        index1 = lcsIndex1[o];
2587        index2 = lcsIndex2[o];
2588
2589        if (index1-i == index2-j && index1-i > 0) {
2590            del = index1-i;
2591            DiffviewDiffsAppend(diffPtr, 'c', i, i+del-1, j, j+del-1);
2592            i += del; j += del;
2593        } else {
2594            del = index1-i;
2595            if (del > 0) {
2596                DiffviewDiffsAppend(diffPtr, 'd', i, i+del-1, j, j);
2597                i += del;
2598            }
2599
2600            del = index2-j;
2601            if (del > 0) {
2602                DiffviewDiffsAppend(diffPtr, 'a', i, i, j, j+del-1);
2603                j += del;
2604            }
2605        }
2606        i++; j++;
2607    }
2608
2609    /* lines left over? mark as many "changed" as possible */
2610    if (limsPtr1->numLines-i > 0 && limsPtr2->numLines-j > 0) {
2611        del = (limsPtr1->numLines-i < limsPtr2->numLines-j)
2612                ? limsPtr1->numLines-i : limsPtr2->numLines-j;
2613        DiffviewDiffsAppend(diffPtr, 'c', i, i+del-1, j, j+del-1);
2614        i += del;
2615        j += del;
2616    }
2617
2618    /* still have lines left over? then mark them added or deleted */
2619    if (i < limsPtr1->numLines) {
2620        del = limsPtr1->numLines - i;
2621        DiffviewDiffsAppend(diffPtr, 'd', i, i+del-1, j, j);
2622        i += del;
2623    }
2624    if (j < limsPtr2->numLines) {
2625        del = limsPtr2->numLines - j;
2626        DiffviewDiffsAppend(diffPtr, 'a', i, i, j, j+del-1);
2627        j += del;
2628    }
2629
2630    /*
2631     * Clean up all memory used during the diff.
2632     */
2633    ckfree((char*)K);
2634    ckfree((char*)lcsIndex1);
2635    ckfree((char*)lcsIndex2);
2636
2637    entryPtr = Tcl_FirstHashEntry(&eqv, &iter);
2638    while (entryPtr) {
2639        listPtr = (Tcl_Obj*)Tcl_GetHashValue(entryPtr);
2640        Tcl_DecrRefCount(listPtr);
2641        entryPtr = Tcl_NextHashEntry(&iter);
2642    }
2643    Tcl_DeleteHashTable(&eqv);
2644
2645    return diffPtr;
2646}
2647
2648/*
2649 * ----------------------------------------------------------------------
2650 * DiffviewDiffsAppend()
2651 *
2652 * Appends a "diff" operation onto a list of diffs.  The list is extended
2653 * automatically, if need be, to store the new element.
2654 * ----------------------------------------------------------------------
2655 */
2656static void
2657DiffviewDiffsAppend(diffsPtr, op, fromIndex1, toIndex1, fromIndex2, toIndex2)
2658    DiffviewDiffs *diffsPtr;  /* diffs being updated */
2659    int op;                   /* diff operation: a/d/c */
2660    int fromIndex1;           /* starts at this line in buffer 1 */
2661    int toIndex1;             /* ends at this line in buffer 1 */
2662    int fromIndex2;           /* starts at this line in buffer 2 */
2663    int toIndex2;             /* ends at this line in buffer 2 */
2664{
2665    DiffviewDiffOp *newOpArray;
2666    int last;
2667
2668    if (diffsPtr->numDiffs >= diffsPtr->maxDiffs) {
2669        if (diffsPtr->maxDiffs == 0) {
2670            diffsPtr->maxDiffs = 10;
2671        } else {
2672            diffsPtr->maxDiffs *= 2;
2673        }
2674        newOpArray = (DiffviewDiffOp*)ckalloc(
2675            diffsPtr->maxDiffs*sizeof(DiffviewDiffOp));
2676
2677        if (diffsPtr->ops) {
2678            memcpy((VOID*)newOpArray, (VOID*)diffsPtr->ops,
2679                diffsPtr->numDiffs*sizeof(DiffviewDiffOp));
2680            ckfree((char*)diffsPtr->ops);
2681        }
2682        diffsPtr->ops = newOpArray;
2683    }
2684
2685    last = diffsPtr->numDiffs;
2686    diffsPtr->ops[last].op         = op;
2687    diffsPtr->ops[last].fromIndex1 = fromIndex1;
2688    diffsPtr->ops[last].toIndex1   = toIndex1;
2689    diffsPtr->ops[last].fromIndex2 = fromIndex2;
2690    diffsPtr->ops[last].toIndex2   = toIndex2;
2691    diffsPtr->numDiffs++;
2692}
2693
2694/*
2695 * ----------------------------------------------------------------------
2696 * DiffviewDiffsFree()
2697 *
2698 * Frees up the storage previously created by calling
2699 * DiffviewDiffsAppend().
2700 * ----------------------------------------------------------------------
2701 */
2702static void
2703DiffviewDiffsFree(diffsPtr)
2704    DiffviewDiffs *diffsPtr;  /* diffs being freed */
2705{
2706    if (diffsPtr->ops) {
2707        ckfree((char*)diffsPtr->ops);
2708    }
2709    ckfree((char*)diffsPtr);
2710}
2711
2712/*
2713 * ----------------------------------------------------------------------
2714 * DiffviewLayoutAdd()
2715 *
2716 * Clears out the layout storage in preparation for building another
2717 * layout.  If the previous number of lines was much less than the
2718 * maximum allocated, then this routine reallocates a smaller storage
2719 * space.  This provides a way to give back a large chunk of memory
2720 * if the contents of the widget is nulled out, for example.
2721 * ----------------------------------------------------------------------
2722 */
2723static void
2724DiffviewLayoutAdd(layoutPtr, linePtr)
2725    DiffviewLayout *layoutPtr;    /* line layout being updated */
2726    DiffviewLayoutLine *linePtr;  /* line being added to layout */
2727{
2728    DiffviewLayoutLine *newLineArray;
2729
2730    /* expand the array as needed */
2731    if (layoutPtr->numLines >= layoutPtr->maxLines) {
2732        if (layoutPtr->maxLines == 0) {
2733            layoutPtr->maxLines = 100;
2734        } else {
2735            layoutPtr->maxLines *= 2;
2736        }
2737        newLineArray = (DiffviewLayoutLine*)ckalloc(
2738            (unsigned)(layoutPtr->maxLines*sizeof(DiffviewLayoutLine)));
2739
2740        if (layoutPtr->lines) {
2741            memcpy((VOID*)newLineArray, (VOID*)layoutPtr->lines,
2742                layoutPtr->numLines*sizeof(DiffviewLayoutLine));
2743            ckfree((char*)layoutPtr->lines);
2744        }
2745        layoutPtr->lines = newLineArray;
2746    }
2747
2748    /* copy the latest line in and bump the count */
2749    memcpy((VOID*)&layoutPtr->lines[layoutPtr->numLines],
2750        (VOID*)linePtr, sizeof(DiffviewLayoutLine));
2751
2752    layoutPtr->numLines++;
2753}
2754
2755/*
2756 * ----------------------------------------------------------------------
2757 * DiffviewLayoutClear()
2758 *
2759 * Clears out the layout storage in preparation for building another
2760 * layout.  If the previous number of lines was much less than the
2761 * maximum allocated, then this routine reallocates a smaller storage
2762 * space.  This provides a way to give back a large chunk of memory
2763 * if the contents of the widget is nulled out, for example.
2764 * ----------------------------------------------------------------------
2765 */
2766static void
2767DiffviewLayoutClear(layoutPtr)
2768    DiffviewLayout *layoutPtr;  /* line layout being freed */
2769{
2770    if (layoutPtr->numLines > 0
2771          && layoutPtr->numLines < layoutPtr->maxLines/3
2772          && layoutPtr->maxLines > 100) {
2773        ckfree((char*)layoutPtr->lines);
2774        layoutPtr->lines = (DiffviewLayoutLine*)ckalloc(
2775            (unsigned)(layoutPtr->numLines * sizeof(DiffviewLayoutLine)));
2776        layoutPtr->maxLines = layoutPtr->numLines;
2777    }
2778    layoutPtr->numLines = 0;
2779}
2780
2781/*
2782 * ----------------------------------------------------------------------
2783 * DiffviewLayoutFree()
2784 *
2785 * Frees the storage associated with the layout of all lines within
2786 * the widget.
2787 * ----------------------------------------------------------------------
2788 */
2789static void
2790DiffviewLayoutFree(layoutPtr)
2791    DiffviewLayout *layoutPtr;  /* line layout being freed */
2792{
2793    if (layoutPtr->lines) {
2794        ckfree((char*)layoutPtr->lines);
2795        layoutPtr->lines = NULL;
2796    }
2797}
Note: See TracBrowser for help on using the repository browser.