Logo Search packages:      
Sourcecode: x11-apps version File versions  Download package

xedit.c

/*
 * Copyright (c) 2002 by The XFree86 Project, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Except as contained in this notice, the name of the XFree86 Project shall
 * not be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization from the
 * XFree86 Project.
 *
 * Author: Paulo C├ęsar Pereira de Andrade
 */

/* $XFree86: xc/programs/xedit/lisp/xedit.c,v 1.25 2003/04/27 18:17:35 tsi Exp $ */

#include "../xedit.h"
#include <X11/Xaw/TextSrcP.h> /* Needs some private definitions */
#include <X11/Xaw/TextSinkP.h>      /* Also needs private definitions... */
#include <X11/Xmu/Xmu.h>
#define XEDIT_LISP_PRIVATE
#include "xedit.h"
#include <signal.h>

/* Initialize to enter lisp */
#define LISP_SETUP()                                  \
    int lisp__running = lisp__data.running

/* XXX Maybe should use ualarm or better, setitimer, but one
 *     second seens good enough to check for interrupts */

#define     ENABLE_SIGALRM()                          \
    old_sigalrm = signal(SIGALRM, SigalrmHandler);          \
    alarm(1)

#define DISABLE_SIGALRM()                             \
    alarm(0);                                         \
    signal(SIGALRM, old_sigalrm)

/* Enter lisp */
#define LISP_ENTER()                                  \
    if (!lisp__running) {                             \
      lisp__data.running = 1;                         \
      XFlush(XtDisplay(textwindow));                        \
      ENABLE_SIGALRM();                         \
      if (sigsetjmp(lisp__data.jmp, 1) != 0) {        \
          DISABLE_SIGALRM();                          \
          lisp__data.running = 0;                     \
          return;                               \
      }                                         \
    }

/* Leave lisp */
#define LISP_LEAVE()                                  \
    if (!lisp__running) {                             \
      DISABLE_SIGALRM();                              \
      LispTopLevel();                                 \
      lisp__data.running = 0;                         \
    }

/*
 * Types
 */
typedef struct {
    XawTextPosition left, right;
    XrmQuark property;
} EntityInfo;

/*
 * Prototypes
 */
static Bool ControlGPredicate(Display*, XEvent*, XPointer);
static ssize_t WriteToStdout(int, const void*, size_t);
static ssize_t WriteToStderr(int, const void*, size_t);
static ssize_t WrapWrite(Widget, const void*, size_t);
static void XeditUpdateModeInfos(void);
static void XeditPrint(Widget, LispObj*, int);
static void XeditInteractiveCallback(Widget, XtPointer, XtPointer);
static void XeditIndentationCallback(Widget, XtPointer, XtPointer);
static LispObj *XeditCharAt(LispBuiltin*, int);
static LispObj *XeditSearch(LispBuiltin*, XawTextScanDirection);

/*
 * Initialization
 */
#ifdef SIGNALRETURNSINT
static int (*old_sigalrm)(int);
#else
static void (*old_sigalrm)(int);
#endif

EditModeInfo *mode_infos;
Cardinal num_mode_infos;

static LispObj *Oauto_modes, *Oauto_mode, *Osyntax_highlight, *Osyntable_indent;

/* Just to make calling interactive reparse easier */
static LispObj interactive_arguments[4];

static LispObj *justify_modes[4];
static LispObj *wrap_modes[3];
static LispObj *scan_types[6];
static LispObj *scan_directions[2];
static LispObj execute_stream;
static LispString execute_string;
static LispObj result_stream;
static LispString result_string;
static XawTextPropertyList **property_lists;
static Cardinal num_property_lists;

/* Some hacks to (at lest try to) avoid problems reentering Xlib while
 * testing for user interrupts */
static volatile int disable_timeout, request_timeout;

extern int pagesize;

static LispBuiltin xeditbuiltins[] = {
    {LispFunction, Xedit_AddEntity, "add-entity offset length identifier"},
    {LispFunction, Xedit_AutoFill, "auto-fill &optional value"},
    {LispFunction, Xedit_Background, "background &optional color"},
    {LispFunction, Xedit_CharAfter, "char-after &optional offset"},
    {LispFunction, Xedit_CharBefore, "char-before &optional offset"},
    {LispFunction, Xedit_ClearEntities, "clear-entities left right"},
    {LispFunction, Xedit_ConvertPropertyList, "convert-property-list name definition"},
    {LispFunction, Xedit_Font, "font &optional font"},
    {LispFunction, Xedit_Foreground, "foreground &optional color"},
    {LispFunction, Xedit_GotoChar, "goto-char offset"},
    {LispFunction, Xedit_HorizontalScrollbar, "horizontal-scrollbar &optional state"},
    {LispFunction, Xedit_Insert, "insert text"},
    {LispFunction, Xedit_Justification, "justification &optional value"},
    {LispFunction, Xedit_LeftColumn, "left-column &optional left"},
    {LispFunction, Xedit_Point, "point"},
    {LispFunction, Xedit_PointMax, "point-max"},
    {LispFunction, Xedit_PointMin, "point-min"},
    {LispFunction, Xedit_PropertyList, "property-list &optional value"},
    {LispFunction, Xedit_ReadText, "read-text offset length"},
    {LispFunction, Xedit_ReplaceText, "replace-text left right text"},
    {LispFunction, Xedit_RightColumn, "right-column &optional right"},
    {LispFunction, Xedit_Scan, "scan offset type direction &key count include"},
    {LispFunction, Xedit_SearchBackward, "search-backward string &optional offset ignore-case"},
    {LispFunction, Xedit_SearchForward, "search-forward string &optional offset ignore-case"},
    {LispFunction, Xedit_VerticalScrollbar, "vertical-scrollbar &optional state"},
    {LispFunction, Xedit_WrapMode, "wrap-mode &optional value"},

      /* This should be available from elsewhere at some time... */
    {LispFunction, Xedit_XrmStringToQuark, "xrm-string-to-quark string"},
};

/*
 * Implementation
 */
/*ARGUSED*/
static Bool
ControlGPredicate(Display *display, XEvent *event, XPointer arguments)
{
    char buffer[2];

    return ((event->type == KeyPress || event->type == KeyRelease) &&
          (event->xkey.state & ControlMask) &&
          XLookupString(&(event->xkey), buffer, sizeof(buffer), NULL, NULL) &&
          buffer[0] == '\a');
}

/*ARGSUSED*/
static
#ifdef SIGNALRETURNSINT
int
#else
void
#endif
SigalrmHandler(int signum)
{
    XEvent event;

    if (disable_timeout) {
      request_timeout = 1;
      return;
    }

    /* Check if user pressed C-g */
    if (XCheckIfEvent(XtDisplay(textwindow), &event, ControlGPredicate, NULL)) {
      XPutBackEvent(XtDisplay(textwindow), &event);
      alarm(0);
      /* Tell a signal was received, print message for SIGINT */
      LispSignal(SIGINT);
    }
    else
      alarm(1);
#ifdef SIGNALRETURNSINT
    return (0);
#endif
}

static ssize_t
WrapWrite(Widget output, const void *buffer, size_t nbytes)
{
    XawTextBlock block;
    XawTextPosition position;

    disable_timeout = 1;
    position = XawTextGetInsertionPoint(output);
    block.firstPos = 0;
    block.format = FMT8BIT;
    block.length = nbytes;
    block.ptr = (String)buffer;
    XawTextReplace(output, position, position, &block);
    XawTextSetInsertionPoint(output, position + block.length);
    disable_timeout = 0;

    if (request_timeout) {
      XFlush(XtDisplay(output));
      request_timeout = 0;
      SigalrmHandler(SIGALRM);
    }

    return ((ssize_t)nbytes);
}

static ssize_t
WriteToStdout(int fd, const void *buffer, size_t nbytes)
{
    return (WrapWrite(textwindow, buffer, nbytes));
}

static ssize_t
WriteToStderr(int fd, const void *buffer, size_t nbytes)
{
    return (WrapWrite(messwidget, buffer, nbytes));
}

void
LispXeditInitialize(void)
{
    int i;
    char *string;
    LispObj *xedit, *list, *savepackage;

    LispSetFileWrite(Stdout, WriteToStdout);
    LispSetFileWrite(Stderr, WriteToStderr);

    justify_modes[0]    = KEYWORD("LEFT");
    justify_modes[1]    = KEYWORD("RIGHT");
    justify_modes[2]    = KEYWORD("CENTER");
    justify_modes[3]    = KEYWORD("FULL");

    wrap_modes[0] = KEYWORD("NEVER");
    wrap_modes[1] = KEYWORD("LINE");
    wrap_modes[2] = KEYWORD("WORD");

    scan_types[0] = KEYWORD("POSITIONS");
    scan_types[1] = KEYWORD("WHITE-SPACE");
    scan_types[2] = KEYWORD("EOL");
    scan_types[3] = KEYWORD("PARAGRAPH");
    scan_types[4] = KEYWORD("ALL");
    scan_types[5] = KEYWORD("ALPHA-NUMERIC");

    scan_directions[0]  = justify_modes[0];
    scan_directions[1]  = justify_modes[1];

    /* Remember value of current package */
    savepackage = PACKAGE;

    /* Create the XEDIT package */
    xedit = LispNewPackage(STRING("XEDIT"), NIL);

    /* Update list of packages */
    PACK = CONS(xedit, PACK);

    /* Temporarily switch to the XEDIT package */
    lisp__data.pack = lisp__data.savepack = xedit->data.package.package;
    PACKAGE = xedit;

    /* Add XEDIT builtin functions */
    for (i = 0; i < sizeof(xeditbuiltins) / sizeof(xeditbuiltins[0]); i++)
      LispAddBuiltinFunction(&xeditbuiltins[i]);

    /* Create these objects in the xedit package */
    Oauto_modes         = STATIC_ATOM("*AUTO-MODES*");
    Oauto_mode          = STATIC_ATOM("AUTO-MODE");
    Osyntax_highlight   = STATIC_ATOM("SYNTAX-HIGHLIGHT");
    Osyntable_indent    = STATIC_ATOM("SYNTABLE-INDENT");

    /*  Import symbols from the LISP and EXT packages */
    for (list = PACK; CONSP(list); list = CDR(list)) {
      string = THESTR(CAR(list)->data.package.name);
      if (strcmp(string, "LISP") == 0 || strcmp(string, "EXT") == 0)
          LispUsePackage(CAR(list));
    }

    /* Restore previous package */
    lisp__data.pack = savepackage->data.package.package;
    PACKAGE = savepackage;

    /* Initialize helper static objects used when executing expressions */
    execute_stream.type = LispStream_t;
    execute_stream.data.stream.source.string = &execute_string;
    execute_stream.data.stream.pathname = NIL;
    execute_stream.data.stream.type = LispStreamString;
    execute_stream.data.stream.readable = 1;
    execute_stream.data.stream.writable = 0;
    execute_string.output = 0;
    result_stream.type = LispStream_t;
    result_stream.data.stream.source.string = &result_string;
    result_stream.data.stream.pathname = NIL;
    result_stream.data.stream.type = LispStreamString;
    result_stream.data.stream.readable = 0;
    result_stream.data.stream.writable = 1;
    result_string.string = XtMalloc(pagesize);
    result_string.space = pagesize;

    /* Initialize interactive edition function arguments */
    /* first argument is syntax table */
    interactive_arguments[0].type = LispCons_t;
    interactive_arguments[0].data.cons.cdr = &interactive_arguments[1];
    /* second argument is where to start reparsing */
    interactive_arguments[1].type = LispCons_t;
    interactive_arguments[1].data.cons.cdr = &interactive_arguments[2];
    /* third argument is where to stop reparsing */
    interactive_arguments[2].type = LispCons_t;
    interactive_arguments[2].data.cons.cdr = &interactive_arguments[3];
    /* fourth argument is interactive flag */
    interactive_arguments[3].type = LispCons_t;
    interactive_arguments[3].data.cons.car = T;
    interactive_arguments[3].data.cons.cdr = NIL;

    /* Load extra functions and data type definitions */
    EXECUTE("(require \"xedit\")");


    /*
     *      This assumes that the *auto-modes* variable is a list where every
     * item has the format:
     *          (regexp string-desc load-file-desc . symbol-name)
     *      Minimal error checking is done.
     */

    if (Oauto_modes->data.atom->a_object) {
      LispObj *desc, *modes = Oauto_modes->data.atom->property->value;

      for (; CONSP(modes); modes = CDR(modes)) {
          list = CAR(modes);

          desc = NIL;
          for (i = 0; i < 3 && CONSP(list); i++, list = CDR(list)) {
            if (i == 1)
                desc = CAR(list);
          }
          if (i == 3 && STRINGP(desc)) {
            mode_infos = (EditModeInfo*)
                XtRealloc((XtPointer)mode_infos, sizeof(EditModeInfo) *
                        (num_mode_infos + 1));
            mode_infos[num_mode_infos].desc = XtNewString(THESTR(desc));
            mode_infos[num_mode_infos].symbol = list;
            mode_infos[num_mode_infos].syntax = NULL;
            ++num_mode_infos;
          }
      }
    }
}

static void
XeditUpdateModeInfos(void)
{
    int i;

    for (i = 0; i < num_mode_infos; i++) {
      if (mode_infos[i].symbol &&
          mode_infos[i].syntax == NULL &&
          XSYMBOLP(mode_infos[i].symbol) &&
          mode_infos[i].symbol->data.atom->a_object)
          mode_infos[i].syntax =
            mode_infos[i].symbol->data.atom->property->value;
    }
}

void
XeditLispExecute(Widget output, XawTextPosition left, XawTextPosition right)
{
    GC_ENTER();
    LISP_SETUP();
    int alloced, return_count;
    XawTextBlock block;
    XawTextPosition position;
    char *string, *ptr;
    LispObj *result, *code, *_cod, *returns;

    LISP_ENTER();

    position = left;
    XawTextSourceRead(XawTextGetSource(textwindow), left, &block, right - left);
    if (block.length < right - left) {
      alloced = 1;
      string = ptr = LispMalloc(right - left);
      memcpy(ptr, block.ptr, block.length);
      position = left + block.length;
      ptr += block.length;
      for (; position < right;) {
          XawTextSourceRead(XawTextGetSource(textwindow),
                        position, &block, right - position);
          memcpy(ptr, block.ptr, block.length);
          position += block.length;
          ptr += block.length;
      }
    }
    else {
      alloced = 0;
      string = block.ptr;
    }

    execute_string.string = string;
    execute_string.length = right - left;
    execute_string.input = 0;
    LispPushInput(&execute_stream);
    _cod = COD;
    result = NIL;
    if ((code = LispRead()) != NULL)
      result = EVAL(code);
    COD = _cod;
    LispPopInput(&execute_stream);

    returns = NIL;
    if (RETURN_COUNT > 0) {
      GC_PROTECT(result);
      returns = _cod = CONS(RETURN(0), NIL);
      GC_PROTECT(returns);
      for (return_count = 1; return_count < RETURN_COUNT; return_count++) {
          RPLACD(_cod, CONS(RETURN(return_count), NIL));
          _cod = CDR(_cod);
      }
    }
    LispFflush(Stdout);
    LispUpdateResults(code, result);
    if (RETURN_COUNT >= 0) {
      XeditPrint(output, result, 1);
      for (; CONSP(returns); returns = CDR(returns))
          XeditPrint(output, CAR(returns), 0);
    }

    if (alloced)
      LispFree(string);
    GC_LEAVE();

    LISP_LEAVE();
}

static void
XeditPrint(Widget output, LispObj *object, int newline)
{
    XawTextBlock block;
    XawTextPosition position;

    result_string.length = result_string.output = 0;
    if (newline) {
      position = XawTextGetInsertionPoint(output);
      if (position != XawTextSourceScan(XawTextGetSource(output),
                                position, XawstEOL,
                                XawsdLeft, 1, False))
          LispSputc(&result_string, '\n');
    }
    LispWriteObject(&result_stream, object);
    LispSputc(&result_string, '\n');

    position = XawTextGetInsertionPoint(output);
    block.firstPos = 0;
    block.format = FMT8BIT;
    block.length = result_string.length;
    block.ptr = result_string.string;
    XawTextReplace(output, position, position, &block);
    XawTextSetInsertionPoint(output, position + block.length);
}

/*
 *  This function is defined here to avoid exporting all the lisp interfaces
 * to the core xedit code.
 */
void
XeditLispSetEditMode(xedit_flist_item *item, LispObj *symbol)
{
    GC_ENTER();
    LISP_SETUP();
    LispObj *syntax, *name;

    item->xldata = (XeditLispData*)XtCalloc(1, sizeof(XeditLispData));

    LISP_ENTER();

    /* Create an object that represents the buffer filename.
     * Note that the entire path is passed to the auto-mode
     * function, so that directory names may be also be used
     * when determining a file type. */
    name = STRING(item->filename);
    GC_PROTECT(name);

    /*  Call the AUTO-MODE function to check if there is a
     * syntax definition for the file being loaded */
    if (symbol == NULL)
      syntax = APPLY1(Oauto_mode, name);
    else
      syntax = APPLY2(Oauto_mode, name, symbol);

    /* Don't need the name object anymore */
    GC_LEAVE();

    if (syntax != NIL) {
      Arg arg[1];
      LispObj arguments;
      XawTextPropertyList *property_list;

      item->xldata->syntax = syntax;

      /* Apply the syntax highlight to the current buffer */
      arguments.type = LispCons_t;
      arguments.data.cons.car = syntax;
      arguments.data.cons.cdr = NIL;
      LispFuncall(Osyntax_highlight, &arguments, 1);

      /*  The previous call added the property list to the widget,
       * remember it when switching sources. */
      XtSetArg(arg[0], XawNtextProperties, &property_list);
      XtGetValues(XawTextGetSink(textwindow), arg, 1);
      item->properties = property_list;

      /* Add callback for interactive changes */
      XtAddCallback(item->source, XtNpropertyCallback,
                  XeditInteractiveCallback, item->xldata);

      /* Update information as a new file may have been loaded */
      XeditUpdateModeInfos();
    }
    else
      item->properties = NULL;

    LISP_LEAVE();
}

void
XeditLispUnsetEditMode(xedit_flist_item *item)
{
    if (item->xldata) {
      XtRemoveCallback(item->source, XtNpropertyCallback,
                   XeditInteractiveCallback, item->xldata);
      XtFree((XtPointer)item->xldata);
      item->xldata = NULL;
    }
}

#define MAX_INFOS 32
/*
 *  This callback tries to do it's best in generating correct output while
 * also doing minimal work/redrawing of the screen. It probably will fail
 * for some syntax-definitions, or will just not properly repaint the
 * screen. In the later case, just press Ctrl+L.
 *  There isn't yet any command to force reparsing of some regions, and if
 * the parser becomes confused, you may need to go to a line, press a space
 * and undo, just to force it to reparse the line, and possibly some extra
 * lines until the parser thinks the display is in sync.
 *  Sometimes it will repaint a lot more of text than what is being requested
 * by this callback, this should be fixed at some time, as for certain cases
 * it is also required some redesign in the Xaw interface.
 */
static void
XeditInteractiveCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
    LISP_SETUP();
    XeditLispData *data = (XeditLispData*)client_data;
    LispObj *syntax = data->syntax;
    XawTextPropertyInfo *info = (XawTextPropertyInfo*)call_data;
    LispObj *result, *syntable;
    XawTextAnchor *anchor;
    XawTextEntity *entity;
    XawTextPosition first, last, left, right, begin, next, tmp, position;
    int i, j, indent;
    TextSrcObject src = (TextSrcObject)w;
    EntityInfo oinfo[MAX_INFOS], ninfo[MAX_INFOS];
    XrmQuark props[MAX_INFOS];
    int num_oinfo, num_ninfo, num_props;
    XmuScanline *clip, *oclip, *nclip;
    XmuSegment segment, *seg;

    if (data->disable_highlight)
      return;

    LISP_ENTER();

    first = XawTextSourceScan(w, 0, XawstAll, XawsdLeft, 1, True);
    last = XawTextSourceScan(w, 0, XawstAll, XawsdRight, 1, True);

    left = info->left;
    right = left + info->block->length;

    /* For now, only call the indent hook if a single character was typed */
    indent = (info->right == left) && (right == left + 1);

    /* Always reparse full lines */
    left = begin = XawTextSourceScan(w, left, XawstEOL, XawsdLeft, 1, False);
    right = next = XawTextSourceScan(w, right, XawstEOL, XawsdRight, 1, False);


    /*  Check properties in the modified text. If a complex nested syntax
     * table was parsed, the newline has it's default property, so, while
     * the newline has a property, backup a line to make sure everything is
     * properly parsed.
     *  Maybe should limit the number of backuped lines, but if the parsing
     * becomes noticeable slow, better to rethink the syntax definition. */
    while (left > first) {
      position = XawTextSourceScan(w, left, XawstEOL, XawsdLeft, 1, True);
      if (XawTextSourceAnchorAndEntity(w, position, &anchor, &entity))
          left = XawTextSourceScan(w, left, XawstEOL, XawsdLeft, 2, False);
      else
          break;
    }

    /*      While the newline after the right position has a "hidden" property,
     * keep incrementing a line to be reparsed. */
    while (right < last) {
      if (XawTextSourceAnchorAndEntity(w, right, &anchor, &entity))
          right = XawTextSourceScan(w, right, XawstEOL, XawsdRight, 2, False);
      else
          break;
    }

#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif

#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

#define STORE_STATE(count, info, from, to)                        \
    (count) = 0;                                      \
    if ((anchor = XawTextSourceFindAnchor(w, (from))) != NULL) {  \
      entity = anchor->entities;                            \
      /* Find first entity in the region to parse */              \
      while (entity &&                                \
             anchor->position + entity->offset + entity->length <=      \
             (from))                                        \
          entity = entity->next;                            \
      /* Loop storing information */                              \
      while (entity &&                                \
          (position = anchor->position + entity->offset) < (to)) {      \
          (info)[(count)].left = MAX(position, (from));           \
          position += entity->length;                             \
          (info)[(count)].right = MIN(position, (to));            \
          (info)[(count)].property = entity->property;            \
          /* If the changes are so complex, user need press Ctrl+L */   \
          if (++(count) >= MAX_INFOS)                             \
            break;                                          \
          if ((entity = entity->next) == NULL &&                  \
            (anchor = XawTextSourceNextAnchor(w, anchor)) != NULL)      \
            entity = anchor->entities;                      \
      }                                               \
    }

    /* Remember old state */
    STORE_STATE(num_oinfo, oinfo, begin, right);

    /* Reparse the lines in the modified/edited range of text */
    interactive_arguments[0].data.cons.car = syntax;
    interactive_arguments[1].data.cons.car = FIXNUM(left);
    interactive_arguments[2].data.cons.car = FIXNUM(right);
    result = APPLY(Osyntax_highlight, &interactive_arguments[0]);
    /* Indent table is the second return value */
    if (RETURN_COUNT)
      syntable = RETURN(0);
    else
      syntable = NIL;

    /* This normally is the same value as right, but the parser may have
     * continued when the syntax table stack did not finish. */
    if (FIXNUMP(result))
      right = FIXNUM_VALUE(result);

    LISP_LEAVE();

    /* Check what have changed */
    STORE_STATE(num_ninfo, ninfo, begin, right);

    /* Initialize to redraw everything. */
    clip = XmuNewScanline(0, begin, right);

#define CLIP_MASK(mask, from, to)                           \
    if ((from) < (to)) {                                    \
      segment.x1 = (from);                                  \
      segment.x2 = (to);                                    \
      XmuScanlineOrSegment((mask), &segment);                     \
    }

    oclip = XmuNewScanline(0, 0, 0);
    nclip = XmuNewScanline(0, 0, 0);

#define CLIP_DEFAULT(mask, from, info, num_info)                  \
    for (tmp = (from), i = 0; i < (num_info); i++) {              \
      CLIP_MASK((mask), tmp, (info)[i].left);                     \
      tmp = (info)[i].right;                                \
    }

    /* First generate masks of regions with the default property */
    CLIP_DEFAULT(oclip, begin, oinfo, num_oinfo);
    CLIP_DEFAULT(nclip, begin, ninfo, num_ninfo);

    /* Store unchanged region in oclip */
    XmuScanlineAnd(oclip, nclip);

    /* Don't need to redraw the region in oclip */
    XmuScanlineXor(clip, oclip);

#define LIST_PROPERTIES(prop, num_prop, info, num_info)                 \
    (num_prop) = 0;                                         \
    for (i = 0; i < (num_info); i++) {                            \
      for (j = 0; j < (num_prop); j++)                      \
          if ((prop)[j] == (info)[i].property)              \
            break;                                          \
      if (j == (num_prop))                                  \
          (prop)[(num_prop)++] = (info)[i].property;              \
    }

    /* Prepare to generate masks of regions of text with defined properties */
    LIST_PROPERTIES(props, num_props, oinfo, num_oinfo);

#define CLIP_PROPERTY(mask, prop, info, num_info)                 \
    for (j = 0; j < (num_info); j++) {                            \
      if ((info)[j].property == (prop)) {                   \
          CLIP_MASK((mask), (info)[j].left, (info)[j].right);           \
      }                                               \
    }

    /* Only care about the old properties, new ones need to be redrawn */
    for (i = 0; i < num_props; i++) {
      XrmQuark property = props[i];

      /* Reset oclip and nclip */
      XmuScanlineXor(oclip, oclip);
      XmuScanlineXor(nclip, nclip);

      /* Generate masks */
      CLIP_PROPERTY(oclip, property, oinfo, num_oinfo);
      CLIP_PROPERTY(nclip, property, ninfo, num_ninfo);

      /* Store unchanged region in oclip */
      XmuScanlineAnd(oclip, nclip);

      /* Don't need to redraw the region in oclip */
      XmuScanlineXor(clip, oclip);
      XmuOptimizeScanline(clip);
    }

    XmuDestroyScanline(oclip);
    XmuDestroyScanline(nclip);

    /* Tell Xaw that need update some regions */
    for (seg = clip->segment; seg; seg = seg->next) {
      for (i = 0; i < src->textSrc.num_text; i++)
          /* This really should have an exported interface... */
          _XawTextNeedsUpdating((TextWidget)(src->textSrc.text[i]),
                          seg->x1, seg->x2 + (seg->x2 > next));
    }
    XmuDestroyScanline(clip);

    data->syntable = syntable;
    /* XXX check lisp__running to know if at the toplevel parsing state */
    if (indent && syntable != NIL && !lisp__running &&
      /* Doing an undo, probably will need an exported interface for this
       * case. Should not change the text now. */
      (!src->textSrc.enable_undo || !src->textSrc.undo_state))
      XtAddCallback(textwindow, XtNpositionCallback,
                  XeditIndentationCallback, data);
}

/*
 * This callback is called if the syntax table where the cursor is located
 * defines an indentation function.
 */
static void
XeditIndentationCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
    LISP_SETUP();
    LispObj *indentp;
    XeditLispData *data = (XeditLispData*)client_data;

    data->disable_highlight = True;
    XtRemoveCallback(w, XtNpositionCallback, XeditIndentationCallback, data);

    LISP_ENTER();

    /* Get pointer to indentation function */
    indentp = APPLY1(Osyntable_indent, data->syntable);

    /* Execute indentation function */
    if (indentp != NIL)
      APPLY2(indentp, data->syntax, data->syntable);

    data->disable_highlight = False;

    LISP_LEAVE();
}

/************************************************************************
 * Builtin functions
 ************************************************************************/
LispObj *
Xedit_AddEntity(LispBuiltin *builtin)
/*
 add-entity offset length identifier
 */
{
    LispObj *offset, *length, *identifier;

    identifier = ARGUMENT(2);
    length = ARGUMENT(1);
    offset = ARGUMENT(0);

    CHECK_INDEX(offset);
    CHECK_INDEX(length);
    CHECK_LONGINT(identifier);

    return (XawTextSourceAddEntity(XawTextGetSource(textwindow), 0, 0, NULL,
                           FIXNUM_VALUE(offset), FIXNUM_VALUE(length),
                           LONGINT_VALUE(identifier)) ? T : NIL);
}

LispObj *
Xedit_AutoFill(LispBuiltin *builtin)
/*
 auto-fill &optional value
 */
{
    Arg arg[1];
    Boolean state;

    LispObj *value;

    value = ARGUMENT(0);

    if (value != UNSPEC) {
      XtSetArg(arg[0], XtNautoFill, value == NIL ? False : True);
      XtSetValues(textwindow, arg, 1);
    }
    else {
      XtSetArg(arg[0], XtNautoFill, &state);
      XtGetValues(textwindow, arg, 1);
      value = state ? T : NIL;
    }

    return (value);
}

LispObj *
Xedit_Background(LispBuiltin *builtin)
/*
 background &optional color
 */
{
    Pixel pixel;
    Arg arg[1];
    XrmValue from, to;

    LispObj *color;

    color = ARGUMENT(0);

    if (color != UNSPEC) {
      CHECK_STRING(color);

      from.size = STRLEN(color);
      from.addr = (XtPointer)THESTR(color);
      to.size = sizeof(Pixel);
      to.addr = (XtPointer)&pixel;

      if (!XtConvertAndStore(XawTextGetSink(textwindow),
                         XtRString, &from, XtRPixel, &to))
          LispDestroy("cannot convert %s to Pixel", STROBJ(color));

      XtSetArg(arg[0], XtNbackground, pixel);
      XtSetValues(textwindow, arg, 1);
    }
    else {
      from.size = sizeof(Pixel);
      from.addr = (XtPointer)&pixel;
      to.size = 0;
      to.addr = NULL;

      XtSetArg(arg[0], XtNbackground, &pixel);
      XtGetValues(XawTextGetSink(textwindow), arg, 1);
      /* This cannot fail */
      XtConvertAndStore(textwindow, XtRPixel, &from, XtRString, &to);

      color = STRING(to.addr);
    }

    return (color);
}

static LispObj *
XeditCharAt(LispBuiltin *builtin, int before)
{
    Widget source = XawTextGetSource(textwindow);
    XawTextPosition first, point, last;
    XawTextBlock block;

    LispObj *offset;

    offset = ARGUMENT(0);
    if (offset != UNSPEC) {
      CHECK_INDEX(offset);
    }

    first = XawTextSourceScan(source, 0, XawstAll, XawsdLeft, 1, True);
    if (FIXNUMP(offset))
      point = FIXNUM_VALUE(offset);
    else
      point = XawTextGetInsertionPoint(textwindow);
    if (before && point > first) {
      XawTextPosition position =
          XawTextSourceScan(source, point, XawstPositions, XawsdLeft, 1, True);

      if (position < point)
          point = position;
      else
          return (NIL);
    }
    last = XawTextSourceScan(source, 0, XawstAll, XawsdRight, 1, True);

    if (point < first || point > last)
      return (NIL);

    XawTextSourceRead(source, point, &block, 1);

    return (block.length ? SCHAR(*(unsigned char*)block.ptr) : NIL);
}

LispObj *
Xedit_CharAfter(LispBuiltin *builtin)
/*
 char-after &optional offset
 */
{
    return (XeditCharAt(builtin, 0));
}

LispObj *
Xedit_CharBefore(LispBuiltin *builtin)
/*
 char-before &optional offset
 */
{
    return (XeditCharAt(builtin, 1));
}

LispObj *
Xedit_ClearEntities(LispBuiltin *builtin)
/*
 clear-entities left right
 */
{
    LispObj *left, *right;

    right = ARGUMENT(1);
    left = ARGUMENT(0);

    CHECK_INDEX(left);
    CHECK_INDEX(right);

    XawTextSourceClearEntities(XawTextGetSource(textwindow),
                         FIXNUM_VALUE(left), FIXNUM_VALUE(right));

    return (T);
}

LispObj *
Xedit_ConvertPropertyList(LispBuiltin *builtin)
/*
 convert-property-list name definition
 */
{
    LispObj *result;
    XawTextPropertyList *property_list;

    LispObj *name, *definition;

    definition = ARGUMENT(1);
    name = ARGUMENT(0);

    CHECK_STRING(name);
    CHECK_STRING(definition);

    result = NIL;
    property_list = XawTextSinkConvertPropertyList(THESTR(name),
                                       THESTR(definition),
                                       topwindow->core.screen,
                                       topwindow->core.colormap,
                                       topwindow->core.depth);

    if (property_list) {
      Cardinal i;

      for (i = 0; i < num_property_lists; i++)
          /* Check if a new property list was created */
          if (property_lists[i]->identifier == property_list->identifier)
            break;

      /* Remember this pointer when asked back for it */
      if (i == num_property_lists) {
          property_lists = (XawTextPropertyList**)
            XtRealloc((XtPointer)property_lists,
                    sizeof(XawTextPropertyList) *
                    (num_property_lists + 1));
          property_lists[num_property_lists++] = property_list;
      }
      result = INTEGER(property_list->identifier);
    }

    return (result);
}

LispObj *
Xedit_Font(LispBuiltin *builtin)
/*
 font &optional font
 */
{
    XFontStruct *font_struct;
    Arg arg[1];
    XrmValue from, to;

    LispObj *font;

    font = ARGUMENT(0);

    if (font != UNSPEC) {
      CHECK_STRING(font);

      from.size = STRLEN(font);
      from.addr = (XtPointer)THESTR(font);
      to.size = sizeof(XFontStruct*);
      to.addr = (XtPointer)&font_struct;

      if (!XtConvertAndStore(textwindow, XtRString, &from, XtRFontStruct, &to))
          LispDestroy("cannot convert %s to FontStruct", STROBJ(font));

      XtSetArg(arg[0], XtNfont, font_struct);
      XtSetValues(textwindow, arg, 1);
    }
    else {
      from.size = sizeof(XFontStruct*);
      from.addr = (XtPointer)&font_struct;
      to.size = 0;
      to.addr = NULL;

      XtSetArg(arg[0], XtNfont, &font_struct);
      XtGetValues(XawTextGetSink(textwindow), arg, 1);
      /* This cannot fail */
      XtConvertAndStore(textwindow, XtRFontStruct, &from, XtRString, &to);

      font = STRING(to.addr);
    }

    return (font);
}

LispObj *
Xedit_Foreground(LispBuiltin *builtin)
/*
 foreground &optional color
 */
{
    Pixel pixel;
    Arg arg[1];
    XrmValue from, to;

    LispObj *color;

    color = ARGUMENT(0);

    if (color != UNSPEC) {
      CHECK_STRING(color);

      from.size = STRLEN(color);
      from.addr = (XtPointer)THESTR(color);
      to.size = sizeof(Pixel);
      to.addr = (XtPointer)&pixel;

      if (!XtConvertAndStore(XawTextGetSink(textwindow),
                         XtRString, &from, XtRPixel, &to))
          LispDestroy("cannot convert %s to Pixel", STROBJ(color));

      XtSetArg(arg[0], XtNforeground, pixel);
      XtSetValues(textwindow, arg, 1);
    }
    else {
      from.size = sizeof(Pixel);
      from.addr = (XtPointer)&pixel;
      to.size = 0;
      to.addr = NULL;

      XtSetArg(arg[0], XtNforeground, &pixel);
      XtGetValues(XawTextGetSink(textwindow), arg, 1);
      /* This cannot fail */
      XtConvertAndStore(textwindow, XtRPixel, &from, XtRString, &to);

      color = STRING(to.addr);
    }

    return (color);
}

LispObj *
Xedit_GotoChar(LispBuiltin *builtin)
/*
 goto-char offset
 */
{
    LispObj *offset;
    XawTextPosition point;

    offset = ARGUMENT(0);

    CHECK_INDEX(offset);
    XawTextSetInsertionPoint(textwindow, FIXNUM_VALUE(offset));
    point = XawTextGetInsertionPoint(textwindow);
    if (point != FIXNUM_VALUE(offset))
      offset = FIXNUM(point);

    return (offset);
}

LispObj *
Xedit_HorizontalScrollbar(LispBuiltin *builtin)
/*
 horizontal-scrollbar &optional state
 */
{
    Arg arg[1];
    XawTextScrollMode scroll;

    LispObj *state;

    state = ARGUMENT(0);

    if (state != UNSPEC) {
      scroll = state == NIL ? XawtextScrollNever : XawtextScrollAlways;
      XtSetArg(arg[0], XtNscrollHorizontal, scroll);
      XtSetValues(textwindow, arg, 1);
    }
    else {
      XtSetArg(arg[0], XtNscrollHorizontal, &scroll);
      XtGetValues(textwindow, arg, 1);
      state = scroll == XawtextScrollAlways ? T : NIL;
    }

    return (state);
}

LispObj *
Xedit_Insert(LispBuiltin *builtin)
/*
 insert text
 */
{
    XawTextPosition point = XawTextGetInsertionPoint(textwindow);
    XawTextBlock block;

    LispObj *text;

    text = ARGUMENT(0);

    CHECK_STRING(text);
    
    block.firstPos = 0;
    block.format = FMT8BIT;
    block.length = STRLEN(text);
    block.ptr = THESTR(text);
    XawTextReplace(textwindow, point, point, &block);
    XawTextSetInsertionPoint(textwindow, point + block.length);

    return (text);
}

LispObj *
Xedit_Justification(LispBuiltin *builtin)
/*
 justification &optional value
 */
{
    int i;
    Arg arg[1];
    XawTextJustifyMode justify;

    LispObj *value;

    value = ARGUMENT(0);

    if (value != UNSPEC) {
      for (i = 0; i < 4; i++)
          if (value == justify_modes[i])
            break;
      if (i >= 4)
          LispDestroy("%s: argument must be "
                  ":LEFT, :RIGHT, :CENTER, or :FULL, not %s",
                  STRFUN(builtin), STROBJ(value));
      XtSetArg(arg[0], XtNjustifyMode, (XawTextJustifyMode)i);
      XtSetValues(textwindow, arg, 1);
    }
    else {
      XtSetArg(arg[0], XtNjustifyMode, &justify);
      XtGetValues(textwindow, arg, 1);
      i = (int)justify;
      if (i <= 0 || i >= 4)
          i = 0;
      value = justify_modes[i];
    }

    return (value);
}

LispObj *
Xedit_LeftColumn(LispBuiltin *builtin)
/*
 left-column &optional left
 */
{
    short left;
    Arg arg[1];

    LispObj *oleft;

    oleft = ARGUMENT(0);

    if (oleft != UNSPEC) {
      CHECK_INDEX(oleft);
      if (FIXNUM_VALUE(oleft) >= 32767)
          left = 32767;
      else
          left = FIXNUM_VALUE(oleft);

      XtSetArg(arg[0], XtNleftColumn, left);
      XtSetValues(textwindow, arg, 1);
    }
    else {
      XtSetArg(arg[0], XtNleftColumn, &left);
      XtGetValues(textwindow, arg, 1);

      oleft = FIXNUM((long)left);
    }

    return (oleft);
}

LispObj *
Xedit_Point(LispBuiltin *builtin)
/*
 point
 */
{
    return (FIXNUM(XawTextGetInsertionPoint(textwindow)));
}

LispObj *
Xedit_PointMax(LispBuiltin *builtin)
/*
 point-max
 */
{
    return (FIXNUM(XawTextSourceScan(XawTextGetSource(textwindow), 0,
                             XawstAll, XawsdRight, 1, True)));
}

LispObj *
Xedit_PointMin(LispBuiltin *builtin)
/*
 point-min
 */
{
    return (FIXNUM(XawTextSourceScan(XawTextGetSource(textwindow), 0,
                             XawstAll, XawsdLeft, 1, True)));
}

LispObj *
Xedit_PropertyList(LispBuiltin *builtin)
/*
 property-list &optional value
 */
{
    Arg arg[1];
    XawTextPropertyList *property_list;

    LispObj *value;

    value = ARGUMENT(0);

    if (value != UNSPEC) {
      Cardinal i;
      XrmQuark quark;

      CHECK_LONGINT(value);
      property_list = NULL;
      quark = LONGINT_VALUE(value);
      for (i = 0; i < num_property_lists; i++)
          if (property_lists[i]->identifier == quark) {
            property_list = property_lists[i];
            break;
          }

      if (property_list) {
          XtSetArg(arg[0], XawNtextProperties, property_list);
          XtSetValues(XawTextGetSink(textwindow), arg, 1);
      }
      else
          /* Maybe should generate an error here */
          value = NIL;
    }
    else {
      XtSetArg(arg[0], XawNtextProperties, &property_list);
      XtGetValues(XawTextGetSink(textwindow), arg, 1);
      if (property_list)
          value = INTEGER(property_list->identifier);
    }

    return (value);
}

LispObj *
Xedit_ReadText(LispBuiltin *builtin)
/*
 read-text offset length
 */
{
    XawTextPosition last = XawTextSourceScan(XawTextGetSource(textwindow), 0,
                                   XawstAll, XawsdRight, 1, True);
    XawTextPosition from, to, len;
    XawTextBlock block;
    char *string, *ptr;

    LispObj *offset, *length;

    length = ARGUMENT(1);
    offset = ARGUMENT(0);

    CHECK_INDEX(offset);
    CHECK_INDEX(length);

    from = FIXNUM_VALUE(offset);
    to = from + FIXNUM_VALUE(length);
    if (from > last)
      from = last;
    if (to > last)
      to = last;

    if (from == to)
      return (STRING(""));

    len = to - from;
    string = LispMalloc(len);

    for (ptr = string; from < to;) {
      XawTextSourceRead(XawTextGetSource(textwindow), from, &block, to - from);
      memcpy(ptr, block.ptr, block.length);
      ptr += block.length;
      from += block.length;
    }

    return (LSTRING2(string, len));
}

LispObj *
Xedit_ReplaceText(LispBuiltin *builtin)
/*
 replace-text left right text
 */
{
    XawTextPosition last = XawTextSourceScan(XawTextGetSource(textwindow), 0,
                                   XawstAll, XawsdRight, 1, True);
    XawTextPosition left, right;
    XawTextBlock block;

    LispObj *oleft, *oright, *text;

    text = ARGUMENT(2);
    oright = ARGUMENT(1);
    oleft = ARGUMENT(0);

    CHECK_INDEX(oleft);
    CHECK_INDEX(oright);
    CHECK_STRING(text);

    left = FIXNUM_VALUE(oleft);
    right = FIXNUM_VALUE(oright);
    if (left > last)
      left = last;
    if (left > right)
      right = left;
    else if (right > last)
      right = last;

    block.firstPos = 0;
    block.format = FMT8BIT;
    block.length = STRLEN(text);
    block.ptr = THESTR(text);
    XawTextReplace(textwindow, left, right, &block);

    return (text);
}

LispObj *
Xedit_RightColumn(LispBuiltin *builtin)
/*
 right-column &optional right
 */
{
    short right;
    Arg arg[1];

    LispObj *oright;

    oright = ARGUMENT(0);

    if (oright != UNSPEC) {
      CHECK_INDEX(oright);
      if (FIXNUM_VALUE(oright) >= 32767)
          right = 32767;
      else
          right = FIXNUM_VALUE(oright);

      XtSetArg(arg[0], XtNrightColumn, right);
      XtSetValues(textwindow, arg, 1);
    }
    else {
      XtSetArg(arg[0], XtNrightColumn, &right);
      XtGetValues(textwindow, arg, 1);

      oright = FIXNUM(right);
    }

    return (oright);
}

LispObj *
Xedit_Scan(LispBuiltin *builtin)
/*
 scan offset type direction &key count include
 */
{
    int i;
    XawTextPosition offset;
    XawTextScanType type;
    XawTextScanDirection direction;
    int count;

    LispObj *ooffset, *otype, *odirection, *ocount, *include;

    include = ARGUMENT(4);
    if (include == UNSPEC)
      include = NIL;
    ocount = ARGUMENT(3);
    odirection = ARGUMENT(2);
    otype = ARGUMENT(1);
    ooffset = ARGUMENT(0);

    CHECK_INDEX(ooffset);
    offset = FIXNUM_VALUE(ooffset);

    for (i = 0; i < 2; i++)
      if (odirection == scan_directions[i])
          break;
    if (i >= 2)
      LispDestroy("%s: direction must be "
                ":LEFT or :RIGHT, not %s",
                STRFUN(builtin), STROBJ(odirection));
    direction = (XawTextScanDirection)i;

    for (i = 0; i < 6; i++)
      if (otype == scan_types[i])
          break;
    if (i >= 6)
      LispDestroy("%s: direction must be "
                ":POSITIONS, :WHITE-SPACE, :EOL, "
                ":PARAGRAPH, :ALL, or :ALPHA-NUMERIC, not %s",
                STRFUN(builtin), STROBJ(otype));
    type = (XawTextScanType)i;

    if (ocount == UNSPEC)
      count = 1;
    else {
      CHECK_INDEX(ocount);
      count = FIXNUM_VALUE(ocount);
    }

    offset = XawTextSourceScan(XawTextGetSource(textwindow),
                         offset, type, direction, count,
                         include != NIL);

    return (FIXNUM(offset));
}

static LispObj *
XeditSearch(LispBuiltin *builtin, XawTextScanDirection direction)
{
    XawTextBlock block;
    XawTextPosition position;

    LispObj *string, *offset, *ignore_case;

    ignore_case = ARGUMENT(2);
    offset = ARGUMENT(1);
    string = ARGUMENT(0);

    CHECK_STRING(string);
    if (offset != UNSPEC) {
      CHECK_INDEX(offset);
      position = FIXNUM_VALUE(offset);
    }
    else
      position = XawTextGetInsertionPoint(textwindow);

    block.firstPos = (ignore_case != UNSPEC && ignore_case != NIL) ? 1 : 0;
    block.format = FMT8BIT;
    block.length = STRLEN(string);
    block.ptr = THESTR(string);
    position = XawTextSourceSearch(XawTextGetSource(textwindow),
                           position, direction, &block);

    return (position != XawTextSearchError ? FIXNUM(position) : NIL);
}


LispObj *
Xedit_SearchBackward(LispBuiltin *builtin)
/*
 search-backward string &optional offset ignore-case
 */
{
    return (XeditSearch(builtin, XawsdLeft));
}

LispObj *
Xedit_SearchForward(LispBuiltin *builtin)
/*
 search-forward string &optional offset ignore-case
 */
{
    return (XeditSearch(builtin, XawsdRight));
}

LispObj *
Xedit_VerticalScrollbar(LispBuiltin *builtin)
/*
 vertical-scrollbar &optional state
 */
{
    Arg arg[1];
    XawTextScrollMode scroll;

    LispObj *state;

    state = ARGUMENT(0);

    if (state != UNSPEC) {
      scroll = state == NIL ? XawtextScrollNever : XawtextScrollAlways;
      XtSetArg(arg[0], XtNscrollVertical, scroll);
      XtSetValues(textwindow, arg, 1);
    }
    else {
      XtSetArg(arg[0], XtNscrollVertical, &scroll);
      XtGetValues(textwindow, arg, 1);
      state = scroll == XawtextScrollAlways ? T : NIL;
    }

    return (state);
}

LispObj *
Xedit_WrapMode(LispBuiltin *builtin)
/*
 wrap-mode &optional value
 */
{
    int i;
    Arg arg[1];
    XawTextWrapMode wrap;

    LispObj *value;

    value = ARGUMENT(0);

    if (value != UNSPEC) {
      for (i = 0; i < 3; i++)
          if (value == wrap_modes[i])
            break;
      if (i >= 3)
          LispDestroy("%s: argument must be "
                  ":NEVER, :LINE, or :WORD, not %s",
                  STRFUN(builtin), STROBJ(value));
      XtSetArg(arg[0], XtNwrap, (XawTextWrapMode)i);
      XtSetValues(textwindow, arg, 1);
    }
    else {
      XtSetArg(arg[0], XtNwrap, &wrap);
      XtGetValues(textwindow, arg, 1);
      i = (int)wrap;
      if (i <= 0 || i >= 3)
          i = 0;
      value = wrap_modes[i];
    }

    return (value);
}

LispObj *
Xedit_XrmStringToQuark(LispBuiltin *builtin)
/*
 xrm-string-to-quark string
 */
{
    LispObj *string;

    string = ARGUMENT(0);

    CHECK_STRING(string);

    return (INTEGER(XrmStringToQuark(THESTR(string))));
}

Generated by  Doxygen 1.6.0   Back to index