Back to index

tetex-bin  3.0
search-internal.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2003-2004 the xdvik development team
00003  * 
00004  * Permission is hereby granted, free of charge, to any person obtaining a copy
00005  * of this software and associated documentation files (the "Software"), to
00006  * deal in the Software without restriction, including without limitation the
00007  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
00008  * sell copies of the Software, and to permit persons to whom the Software is
00009  * furnished to do so, subject to the following conditions:
00010  * 
00011  * The above copyright notice and this permission notice shall be included in
00012  * all copies or substantial portions of the Software.
00013  * 
00014  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00015  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00016  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
00017  * PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
00018  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
00019  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
00020  * OTHER DEALINGS IN THE SOFTWARE.
00021  */
00022 
00023 /*
00024   How dvi search works:
00025 
00026   (1) Map the search string passed by the user from resource.text_encoding
00027       to UTF-8 (currently only maps ISO_8859-1), and create a regexp from
00028       the string if needed.
00029 
00030       If case-insensitive matching is activated and regexp search is not
00031       active, the search string is converted to lowercase. For regexp
00032       searches, the flag available with regcomp to ignore case is used.
00033   
00034   (2) Scan 2 adjacent pages (so that we can also find matches across page
00035       boundaries), collecting the text into a char *buffer.
00036       This involves the following steps:
00037 
00038       - Map characters from wide_ubyte to uint32_t encoding (ucs-4),
00039         using and Adobe names lookup table (for Postscript Type1
00040         fonts) or font-specific character info (for Metafont fonts).
00041        
00042         This step also maps accented glyphs to composite glyphs,
00043        expands ligatures to separate characters (e.g. `ffi' to
00044        `f'`f'`i'), and tries to detect word boundaries and line
00045        breaks.
00046 
00047        If case-insensitive matching is activated, the characters are
00048        also lowercased in this step.
00049        
00050        Optionally, hyphenation can be removed in this step.
00051        
00052        Routine: do_char() in dvi-draw.c (must be in dvi_draw.c since
00053        we need access to drawing-related stuff like `currinf' to get
00054        information about the current font, position in the DVI file
00055        etc.).
00056 
00057       - Save the uint32_t characters into a char *buffer, mapping to
00058         utf-8.
00059       
00060   (3) Search the buffer from (2) for the string from (1). If a match
00061       has been found, the page is scanned again to store the bounding
00062       box information of the matched text for highlighting it.
00063 */
00064 
00065 
00066 
00067 #include "xdvi-config.h"
00068 #include "xdvi.h"
00069 
00070 #define SWITCH_TO_UTF8 1
00071 
00072 #if HAVE_REGEX_H
00073 #include <regex.h>
00074 #endif
00075 
00076 #if HAVE_ICONV_H
00077 #include <iconv.h>
00078 #endif
00079 
00080 #include <locale.h>
00081 
00082 #if USE_LANGINFO
00083 #include <langinfo.h>
00084 #endif
00085 
00086 #include <ctype.h>
00087 
00088 #include "dvi-init.h" /* for total_pages */
00089 #include "dvi-draw.h"
00090 #include "search-internal.h"
00091 
00092 #include "search-dialog.h"
00093 #include "message-window.h"
00094 #include "statusline.h"
00095 #include "events.h"
00096 #include "encodings.h"
00097 #include "pagesel.h"
00098 #include "events.h"
00099 #include "util.h"
00100 #include "x_util.h"
00101 #include "string-utils.h"
00102 #include "mag.h"
00103 #include "pagehist.h"
00104 
00105 #define DEBUG_SEARCH 0
00106 /* #define MATCH_HIGHLIGHT_INVERTED 1 */
00107 
00108 
00109 /* margins for highlighting the match */
00110 const int BBOX_LOWER_MARGIN = 2;
00111 const int BBOX_UPPER_MARGIN = 1;
00112 
00113 #if 0
00114 #define GET_BBOX(bbox, shrink, x, y, w, h)                            \
00115        x = (bbox.ulx / (double)shrink + 0.5) - BBOX_UPPER_MARGIN;     \
00116        y = (bbox.uly / (double)shrink + 0.5) - BBOX_UPPER_MARGIN;     \
00117        w = ((bbox.lrx - bbox.ulx) / (double)shrink) + 0.5             \
00118            + BBOX_UPPER_MARGIN + BBOX_LOWER_MARGIN + 1;        \
00119        h = ((bbox.lry - bbox.uly) / (double)shrink) + 0.5             \
00120            + BBOX_UPPER_MARGIN + BBOX_LOWER_MARGIN + 2;
00121 #endif /* 0 */
00122 /* use integer arithmetic */
00123 #define GET_BBOX(bbox, shrink, x, y, w, h)                            \
00124        x = (bbox.ulx + shrink / 2) / shrink - BBOX_UPPER_MARGIN;      \
00125        y = (bbox.uly + shrink / 2) / shrink - BBOX_UPPER_MARGIN;      \
00126        w = (bbox.lrx - bbox.ulx + shrink - 1) / shrink                \
00127            + BBOX_UPPER_MARGIN + BBOX_LOWER_MARGIN + 1;        \
00128        h = (bbox.lry - bbox.uly + shrink - 1) / shrink                \
00129            + BBOX_UPPER_MARGIN + BBOX_LOWER_MARGIN + 2;
00130 
00131 /* access from drawing routines */
00132 static struct word_info *m_info = NULL;
00133 static int m_match_page = -1;
00134 
00135 /* Force restarting search if user switches pages. E.g. if
00136    the current match is the last of 3 matches on the page,
00137    and user re-selects the page, the search should start again
00138    with match 1. Similarly, if current page is second of two
00139    pages scanned, and user clicks on first, search shouldn't continue
00140    on second page but restart on current one.
00141 */
00142 static Boolean m_changed_page = False;
00143 
00144 static Boolean m_highlight_region_changed = True;
00145 
00146 void
00147 search_signal_page_changed(void)
00148 {
00149     m_changed_page = True;
00150 }
00151 
00152 
00153 static void
00154 reset_info(struct word_info *w_info)
00155 {
00156     if (w_info == NULL)
00157        return;
00158 
00159     free(w_info->txt_buf);
00160     w_info->txt_buf = NULL;
00161     w_info->txt_buf_size = 0;
00162     w_info->curr_buf_idx = 0;
00163 
00164     free(w_info->bboxes);
00165     w_info->bboxes = NULL;
00166     w_info->bboxes_size = 0;
00167     w_info->bboxes_idx = 0;
00168 
00169     m_highlight_region_changed = True;
00170 }
00171 
00172 
00173 static void
00174 generate_highlight_expose(const struct word_info *info)
00175 {
00176     int x1 = INT_MAX, y1 = INT_MAX, x2 = 0, y2 = 0;
00177     size_t i;
00178     for (i = 0; info->bboxes != NULL && i <= info->bboxes_idx && info->bboxes[i].ulx < INT_MAX; i++) {
00179        if (info->bboxes[i].ulx < x1)
00180            x1 = info->bboxes[i].ulx;
00181        if (info->bboxes[i].uly < y1)
00182            y1 = info->bboxes[i].uly;
00183        if (info->bboxes[i].lrx > x2)
00184            x2 = info->bboxes[i].lrx;
00185        if (info->bboxes[i].lry > y2)
00186            y2 = info->bboxes[i].lry;
00187     }
00188 #if 0
00189     fprintf(stderr, "region for exposure: %d,%d to %d,%d\n",
00190            x1, y1, x2, y2);
00191 #endif /* 0 */
00192     x1 -= MAX(BBOX_UPPER_MARGIN, BBOX_LOWER_MARGIN) - 2;
00193     y1 -= MAX(BBOX_UPPER_MARGIN, BBOX_LOWER_MARGIN) - 2;
00194     x2 += MAX(BBOX_UPPER_MARGIN, BBOX_LOWER_MARGIN) + 2;
00195     y2 += MAX(BBOX_UPPER_MARGIN, BBOX_LOWER_MARGIN) + 2;
00196     clearexpose(&mane, x1, y1, x2 - x1, y2 - y1);
00197 }
00198 
00199 static void
00200 draw_bboxes(const struct word_info *info)
00201 {
00202     size_t i;
00203     static GC bboxGC = 0;
00204     static Boolean highlight_inverted_bak;
00205 
00206     if (bboxGC != 0 && highlight_inverted_bak != resource.match_highlight_inverted) {
00207        XFreeGC(DISP, bboxGC);
00208        bboxGC = 0;
00209     }
00210     highlight_inverted_bak = resource.match_highlight_inverted;
00211     
00212     if (resource.match_highlight_inverted) {
00213        if (MAGNIFIER_ACTIVE && !INSIDE_MANE_WIN)
00214            return;
00215        
00216        TRACE_FIND((stderr, "-- EXPOSED region: x %d, y %d, w %d, h %d",
00217                   globals.win_expose.min_x, globals.win_expose.min_y, globals.win_expose.max_x - globals.win_expose.min_x, globals.win_expose.max_y - globals.win_expose.min_y));
00218        
00219        if (bboxGC == 0) {
00220            XGCValues values;
00221            unsigned long valuemask;
00222            
00223            values.function = GXinvert; /* was: GXxor; see bug #850788 */
00224            if (values.function == GXinvert) {
00225               valuemask = GCFunction;
00226            }
00227            else {
00228               values.foreground = WhitePixelOfScreen(SCRN) ^ BlackPixelOfScreen(SCRN);
00229 /*            fprintf(stderr, "foreground: 0x%lx, white pixel: 0x%lx, black pixel: 0x%lx\n", */
00230 /*                   values.foreground, WhitePixelOfScreen(SCRN), BlackPixelOfScreen(SCRN)); */
00231               valuemask = GCFunction | GCForeground;
00232            }
00233            bboxGC = XCreateGC(DISP, XtWindow(globals.widgets.top_level), valuemask, &values);
00234        }
00235     }
00236     else if (bboxGC == 0) {
00237        XGCValues values;
00238        values.function = GXcopy;
00239        values.line_width = 2;
00240        values.cap_style = CapRound;
00241        values.foreground = resource.hl_Pixel;
00242        values.background = resource.back_Pixel;
00243        bboxGC = XCreateGC(DISP, XtWindow(globals.widgets.top_level),
00244                         GCFunction | GCLineWidth | GCCapStyle | GCForeground | GCBackground, &values);
00245     }
00246 
00247     TRACE_FIND((stderr, "bboxes_idx: %lu", (unsigned long)info->bboxes_idx));
00248     for (i = 0; info->bboxes != NULL && i <= info->bboxes_idx && info->bboxes[i].ulx < INT_MAX; i++) {
00249        int x, y, w, h;
00250 
00251        if (info->bboxes[i].ulx > 10000 || info->bboxes[i].uly > 10000) {
00252            TRACE_FIND((stderr, "skipping box: x %d, y %d",
00253                      info->bboxes[i].ulx, info->bboxes[i].uly));
00254            continue;
00255        }
00256        
00257        GET_BBOX(info->bboxes[i], currwin.shrinkfactor, x, y, w, h);
00258        TRACE_FIND((stderr, "DRAWING box: x %d, y %d, w %d, h %d; shrink: %d",
00259                   x, y, w, h, currwin.shrinkfactor));
00260        if (resource.match_highlight_inverted) {
00261            if (clip_region(&x, &y, &w, &h)) {
00262               TRACE_FIND((stderr, "CLIPPED box: x %d, y %d, w %d, h %d",
00263                          x, y, w, h));
00264               TEST_DELAY("Redrawing BBOX ............... ");
00265               XFillRectangle(DISP, mane.win, bboxGC, x, y, w, h);
00266               TEST_DELAY("Inverting BBOX ............... ");
00267            }
00268        }
00269        /*     XDrawRectangle(DISP, mane.win, globals.gc.high, x, y, w + 0, h + 0); */
00270        /*     clearexpose(&mane, x, y, w + 3, h + 3); */
00271        /*     XDrawRectangle(DISP, mane.win, globals.gc.high, x, y, w + 2, h + 2); */
00272        /*         XDrawRectangle(DISP, mane.win, globals.gc.high, x + 1, y + 1, w, h); */
00273        else {
00274            static XPoint points[5] = { {0,0}, {0,0}, {0,0}, {0,0}, {0,0} };
00275            
00276            h++;
00277            
00278            points[0].x = x;
00279            points[0].y = y;
00280            
00281            points[1].x = w;
00282            
00283            points[2].y = h;
00284            
00285            points[3].x = -w;
00286            
00287            points[4].y = -h;
00288            
00289            XDrawLines(DISP, mane.win, bboxGC, points, 5, CoordModePrevious);
00290        }
00291 
00292        if (!MAGNIFIER_ACTIVE) {
00293            /* note: inverted y_max y_min, since we want to display the full box here */
00294            scroll_page_if_needed(x + w, x, y + h, y);
00295        }
00296     }
00297 }
00298 
00299 /* return true if we have a match on page pageno */
00300 Boolean
00301 search_have_match(int pageno)
00302 {
00303 /*     fprintf(stderr, "pageno: %d --- m_info: %p\n", pageno, m_info); */
00304 /*     fprintf(stderr, "m_match_page: %d\n", m_match_page); */
00305     return m_info != NULL && m_match_page == pageno;
00306 }
00307 
00308 /* TODO: Speed this up, by doing something like bsearch? */
00309 /*
00310   Returns 2 if we're at the 1st or last character of the bbox, 1 if
00311   inside the bbox, and 0 else (currently the return value 2 is unused).
00312 */
00313 int
00314 search_inside_bbox_match(int x, int y)
00315 {
00316     size_t i;
00317 
00318     ASSERT(m_info != NULL, "inside_match musn't be called with m_info == NULL!");
00319     TRACE_FIND((stderr, "m_info->bboxes: %p; idx: %lu", (void *)m_info->bboxes, (unsigned long)m_info->bboxes_idx));
00320     for (i = 0; m_info->bboxes != NULL && i <= m_info->bboxes_idx && m_info->bboxes[i].ulx < INT_MAX; i++) {
00321        TRACE_FIND((stderr, "inside_bbox_match: %d, %d", x, y));
00322        TRACE_FIND((stderr, "x: %d, y: %d, x2: %d, y2: %d",
00323                   (int)(m_info->bboxes[i].ulx / (double)currwin.shrinkfactor + 0.5),
00324                   (int)(m_info->bboxes[i].uly / (double)currwin.shrinkfactor + 0.5),
00325                   (int)(m_info->bboxes[i].lrx / (double)currwin.shrinkfactor + 0.5),
00326                   (int)(m_info->bboxes[i].lry / (double)currwin.shrinkfactor + 0.5)));
00327 
00328        if (x >= (int)(m_info->bboxes[i].ulx / (double)currwin.shrinkfactor + 0.5)
00329            && x <= (int)(m_info->bboxes[i].lrx / (double)currwin.shrinkfactor + 0.5)
00330            && y >= (int)(m_info->bboxes[i].uly / (double)currwin.shrinkfactor + 0.5)
00331            && y <= (int)(m_info->bboxes[i].lry / (double)currwin.shrinkfactor + 0.5)) {
00332            TRACE_FIND((stderr, "%d == %d",
00333                      x - 1, (int)(m_info->bboxes[i].ulx / (double)currwin.shrinkfactor + 0.5)));
00334            TRACE_FIND((stderr, "%d == %d",
00335                      y - 1, (int)(m_info->bboxes[i].uly / (double)currwin.shrinkfactor + 0.5)));
00336            if (x - 1 == (int)(m_info->bboxes[i].ulx / (double)currwin.shrinkfactor + 0.5)
00337               && y - 1 == (int)(m_info->bboxes[i].uly / (double)currwin.shrinkfactor + 0.5))
00338               return 2;
00339            else
00340               return 1;
00341        }
00342     }
00343     return 0;
00344 }
00345 
00346 void
00347 search_draw_inverted_regions(void)
00348 {
00349     if (m_info != NULL) {
00350        draw_bboxes(m_info);
00351     }
00352 }
00353 
00354 
00355 /*
00356  * shift everything in info down by 1 pages, so that info for page 1 becomes
00357  * info for page 0.
00358  */
00359 static void
00360 shift_info_down(struct search_info *searchinfo, struct word_info *info, struct page_mapping *page_mapping)
00361 {
00362     size_t initial_offset = 0;
00363     if (page_mapping[0].offset != -1) { /* remember value */
00364        initial_offset = page_mapping[0].offset;
00365     }
00366     
00367     if (globals.debug & DBG_FIND) {
00368        int i;
00369        fprintf(stderr, "current page_mapping:\n");
00370        for (i = 0; i < 2; i++) {
00371            fprintf(stderr, "%d: %d\n", page_mapping[i].pageno, page_mapping[i].offset);
00372        }
00373     }
00374     
00375     TRACE_FIND((stderr, "initial offset: %lu", (unsigned long)initial_offset));
00376 
00377     page_mapping[0].offset = page_mapping[1].offset - initial_offset;
00378     page_mapping[0].pageno = page_mapping[1].pageno;
00379     ASSERT(page_mapping[0].offset >= 0, "new index mustn't be negative!\n");
00380     page_mapping[1].offset = page_mapping[1].pageno = -1;
00381     
00382     TRACE_FIND((stderr, "new val at 0: %d; curr_idx: %lu, len: %lu",
00383               page_mapping[0].offset,
00384               (unsigned long)(info->curr_buf_idx - initial_offset),
00385               (unsigned long)(strlen(info->txt_buf + initial_offset))));
00386 
00387     /* move the text buffer down, and update its current index accordingly */
00388     ASSERT(info->curr_buf_idx >= initial_offset, "info->curr_buf_idx mustn't become negative!");
00389     memmove(info->txt_buf, info->txt_buf + initial_offset, info->curr_buf_idx - initial_offset + 1);
00390     info->curr_buf_idx -= initial_offset;
00391     TRACE_FIND((stderr, "new index: %lu", (unsigned long)info->curr_buf_idx));
00392 
00393     searchinfo->from_pos -= initial_offset;
00394     searchinfo->to_pos -= initial_offset;
00395     if (searchinfo->from_pos < 0)
00396        searchinfo->from_pos = 0;
00397     if (searchinfo->to_pos < 0)
00398        searchinfo->to_pos = 0;
00399     TRACE_FIND((stderr, "new offsets: from=%d, to=%d", searchinfo->from_pos, searchinfo->to_pos));
00400 }
00401 
00402 /*
00403  * shift everything in info up by 1 page, so that info for page 0 becomes info
00404  * for page 1.
00405  */
00406 static void
00407 shift_info_up(struct word_info *info, struct page_mapping *page_mapping)
00408 {
00409     if (globals.debug & DBG_FIND) {
00410        int i;
00411        fprintf(stderr, "current page_mapping:\n");
00412        for (i = 0; i < 2; i++) {
00413            fprintf(stderr, "%d: %d\n", page_mapping[i].pageno, page_mapping[i].offset);
00414        }
00415     }
00416     
00417     ASSERT(page_mapping[0].offset > 0, "info->curr_buf_idx mustn't become negative!");
00418     info->curr_buf_idx = page_mapping[0].offset;
00419     TRACE_FIND((stderr, "new index: %lu", (unsigned long)info->curr_buf_idx));
00420 
00421     page_mapping[1].offset = page_mapping[0].offset;
00422     page_mapping[1].pageno = page_mapping[0].pageno;
00423     page_mapping[0].offset = page_mapping[0].pageno = -1;
00424     
00425 }
00426 
00427 static void
00428 append_to_info(struct word_info *info, const char *str)
00429 {
00430     size_t len = strlen(str);
00431     size_t new_size = info->txt_buf_size;
00432     while (len >= new_size) { /* space for trailing 0 */
00433        new_size++;
00434     }
00435     if (new_size > info->txt_buf_size) {
00436        info->txt_buf_size = new_size;
00437        info->txt_buf = xrealloc(info->txt_buf, sizeof *(info->txt_buf) * info->txt_buf_size);
00438     }
00439     memcpy(info->txt_buf + info->curr_buf_idx, str, len + 1); /* also copy trailing 0 */
00440     info->curr_buf_idx += len;
00441 }
00442 
00443 /* append info2 to info1, enlarging info1 as needed. */
00444 static void
00445 append_info(struct word_info *info1, const struct word_info *info2)
00446 {
00447     if (info2->txt_buf_size > 0) {
00448        info1->txt_buf = xrealloc(info1->txt_buf, sizeof *(info1->txt_buf)
00449                               * (info1->txt_buf_size + info2->txt_buf_size));
00450        memcpy(info1->txt_buf + info1->curr_buf_idx, info2->txt_buf, info2->txt_buf_size);
00451        info1->txt_buf_size += info2->txt_buf_size;
00452        info1->curr_buf_idx += info2->curr_buf_idx;
00453     }
00454 }
00455 
00456 /* prepend info1 to info2, enlarging info2 as needed. */
00457 static void
00458 prepend_info(const struct word_info *info1, struct word_info *info2)
00459 {
00460     if (info1->txt_buf_size > 0) {
00461        info2->txt_buf = xrealloc(info2->txt_buf, sizeof *(info2->txt_buf)
00462                               * (info2->txt_buf_size + info1->txt_buf_size + 1));
00463        memmove(info2->txt_buf + info1->curr_buf_idx, info2->txt_buf, info2->curr_buf_idx);
00464        info2->txt_buf[info1->curr_buf_idx + info2->curr_buf_idx] = '\0';
00465        memcpy(info2->txt_buf, info1->txt_buf, info1->curr_buf_idx);
00466        /*     TRACE_FIND((stderr, "prepend_info: info2->txt_buf is: |%s|", info2->txt_buf)); */
00467        info2->txt_buf_size += info1->txt_buf_size;
00468        info2->curr_buf_idx += info1->curr_buf_idx;
00469     }
00470 }
00471 
00472 #if HAVE_REGEX_H
00473 /* convert the encoding part (between the `.' and the `@') of the
00474    locale string passed as argument to UTF-8, and return the result
00475    in a newly allocated string which the caller is responsible for
00476    free()ing.
00477 */
00478 static char *
00479 locale_to_utf8(const char *locale)
00480 {
00481     char *utf8_locale = xstrdup(locale);
00482     char *ptr;
00483     if ((ptr = strchr(utf8_locale, '.')) != NULL) {
00484        char *ptr2, *rest = "";
00485        if ((ptr2 = strchr(utf8_locale, '@')) != NULL)
00486            rest = xstrdup(ptr2);
00487 
00488        utf8_locale = xrealloc(utf8_locale,
00489                             ptr - utf8_locale + strlen(".utf8") + strlen(rest) + 1);
00490        *ptr = '\0';
00491        utf8_locale = strcat(utf8_locale, ".utf8");
00492        if (ptr2 != NULL) {
00493            utf8_locale = strcat(utf8_locale, rest);
00494            free(rest);
00495        }
00496     }
00497     else {
00498        utf8_locale = xstrcat(utf8_locale, ".utf8");
00499     }
00500     return utf8_locale;
00501 }
00502 #endif /* HAVE_REGEX_H */
00503 
00504 static Boolean
00505 is_utf8_ideograph(const unsigned char *p)
00506 {
00507     int len;
00508     uint32_t ucs4;
00509     
00510     if ((len = utf8_to_ucs4((const char *)p, &ucs4, strlen((const char*)p))) <= 0)
00511        return False;
00512     
00513     return is_ideograph(ucs4);
00514 }
00515 
00516 
00517 static void
00518 dump_buffer(const struct word_info *info, size_t offset, FILE *fp, outputFormatT fmt)
00519 {
00520     size_t i = offset, len, tot_len;
00521 
00522     if (info->txt_buf == NULL)
00523        return;
00524     
00525     tot_len = strlen(info->txt_buf);
00526     while (i < tot_len) {
00527        if (fmt == FMT_UTF8) { /* just dump as-is */
00528            fputc(info->txt_buf[i++], fp);
00529        }
00530        else { /* convert to iso-latin1, rendering unknown characters as `?' */
00531            uint32_t ucs4;
00532            const char *ret;
00533 
00534            /* first apply normalization heurisitcs also used by search */
00535            len = utf8_to_ucs4(info->txt_buf + i, &ucs4, strlen(info->txt_buf + i));
00536            if ((ret = search_normalize_chars(ucs4)) != NULL)
00537               fputs(ret, fp);
00538            else if (ucs4 <= 0xff) /* in iso-latin1 range */
00539               fputc((unsigned char)ucs4, fp);
00540            else
00541               fprintf(fp, "\\%.4lX", (unsigned long)ucs4);
00542            i += len;
00543        }
00544     }
00545 }
00546 
00547 static void
00548 scan_page(FILE *fp, int pageno, struct word_info *w_info)
00549 {
00550     off_t pos_save;
00551     static ubyte my_scan_buffer[DVI_BUFFER_LEN];
00552     struct drawinf currinf_save;
00553     struct scan_info info;
00554     ubyte maxchar_save;
00555 
00556     reinit_text_scan(); /* to reset scanning heuristics (linebreaks etc.) */
00557     info.data = (void *)w_info;
00558     info.geom_special = NULL; /* no procedure here */
00559     
00560     /* Save file position */
00561     pos_save = save_file_status(globals.dvi_file.bak_fp, &currinf_save, &maxchar_save);
00562     
00563     lseek(fileno(fp), pageinfo_get_offset(pageno), SEEK_SET);
00564     memset((char *)&currinf.data, 0, sizeof currinf.data);
00565     currinf.tn_table_len = TNTABLELEN;
00566     currinf.tn_table = tn_table;
00567     currinf.tn_head = tn_head;
00568 
00569     /* point currinf to our own buffer: */
00570     G_dvi_buf_ptr = my_scan_buffer;
00571     
00572     currinf.pos = currinf.end = G_dvi_buf_ptr;
00573     currinf.virtual = NULL;
00574     
00575     geom_scan(text_do_char, fp, &info, pageno);
00576 
00577     /* Restore file status.  */
00578     restore_file_status(globals.dvi_file.bak_fp, currinf_save, maxchar_save, pos_save);
00579 }
00580 
00581 
00582 static Boolean
00583 do_scan_page(struct word_info *w_info, struct search_settings *settings,
00584             struct page_mapping *page_mapping, int buffer_offset, int pageno,
00585             searchDirectionT direction)
00586 {
00587     struct word_info page_info = { NULL, 0, 0, NULL, 0, 0, NULL, NULL, 0, False, True, False };
00588     page_info.settings = settings;
00589     ASSERT(buffer_offset >= 0, "buffer_offset must have been initialized");
00590     page_info.buffer_offset = buffer_offset;
00591     TRACE_FIND((stderr, "scanning page: %d; from_pos: %d", pageno, settings->searchinfo->from_pos));
00592 
00593     if (read_events(EV_NOWAIT) & EV_GE_FIND_CANCEL) {
00594        TRACE_FIND((stderr, "interrupted!!"));
00595        return False;
00596     }
00597     
00598     scan_page(globals.dvi_file.bak_fp, pageno, &page_info);
00599 
00600     /*     TRACE_FIND((stderr, "scanned buffer of length %d on page %d; contents: |%s|", */
00601     /*               page_info.curr_buf_idx, pageno, page_info.txt_buf)); */
00602     
00603     /* terminate page */
00604     append_to_info(&page_info, "\n");
00605     if (direction == SEARCH_DOWN) {
00606        /* append to existing info */
00607        append_info(w_info, &page_info);
00608        page_mapping->offset = w_info->curr_buf_idx;
00609     }
00610     else {
00611        prepend_info(&page_info, w_info);
00612        /*     TRACE_FIND((stderr, "buffer after prepending: |%s|", w_info->txt_buf)); */
00613        page_mapping->offset = page_info.curr_buf_idx;
00614     }
00615     page_mapping->pageno = pageno;
00616     
00617     return True;
00618 }
00619 
00620 #if HAVE_REGEX_H
00621 static void
00622 report_regexp_error(int errcode, regex_t *regex, const char *term, int flag)
00623 {
00624     size_t n = regerror(errcode, regex, NULL, 0);
00625     char *err_buf = xmalloc(n);
00626     regerror(errcode, regex, err_buf, n);
00627     XBell(DISP, 0);
00628     popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"),
00629                 MSG_ERR,
00630                 NULL,
00631                 "Could not %s regular expression \"%s\": %s\n",
00632                 flag == 0 ? "compile" : "execute",
00633                 term, err_buf);
00634     free(err_buf);
00635 }
00636 #endif /* HAVE_REGEX_H */
00637 
00638 static void
00639 try_match(const struct word_info *info,
00640          const struct search_settings *settings,
00641          struct search_info *searchinfo)
00642 {
00643     char *res = NULL;
00644     size_t from_pos = 0;
00645     
00646     ASSERT(info->curr_buf_idx == strlen(info->txt_buf), "");
00647     TRACE_FIND((stderr, "buffer index: %lu, len: %lu; from: %lu",
00648               (unsigned long)info->curr_buf_idx,
00649               (unsigned long)strlen(info->txt_buf),
00650               (unsigned long)searchinfo->from_pos));
00651     if (searchinfo->from_pos != -1)
00652        from_pos = searchinfo->from_pos;
00653     
00654     if (settings->direction == SEARCH_DOWN) {
00655        TRACE_FIND((stderr, "trying to match |%s| from %lu; total: %lu",
00656                   settings->utf8_term,
00657                   (unsigned long)from_pos,
00658                   (unsigned long)strlen(info->txt_buf)));
00659 
00660        res = strstr(info->txt_buf + from_pos, settings->utf8_term);
00661     }
00662     else {
00663        size_t curr_pos = 0;
00664        char *res_bak = NULL;
00665 
00666        TRACE_FIND((stderr, "UP; trying to match |%s| from %lu, %lu",
00667                   settings->utf8_term,
00668                   (unsigned long)curr_pos, (unsigned long)from_pos));
00669        TRACE_FIND((stderr, "buf: |%s|", info->txt_buf + curr_pos));
00670        while (curr_pos <= from_pos) {
00671            res_bak = res;
00672            res = strstr(info->txt_buf + curr_pos, settings->utf8_term);
00673            if (res != NULL) {
00674               curr_pos = res - info->txt_buf + strlen(settings->utf8_term);
00675            }
00676            else {
00677               break;
00678            }
00679        }
00680        res = res_bak;
00681     }
00682     if (res != NULL) {
00683        searchinfo->have_match = True;
00684        searchinfo->from_pos = res - info->txt_buf;
00685        searchinfo->to_pos = searchinfo->from_pos + strlen(settings->utf8_term);
00686     }
00687     else {
00688        searchinfo->have_match = False;
00689     }
00690 }
00691 
00692 #if HAVE_REGEX_H
00693 static Boolean
00694 try_regexp_match(regex_t *regex,
00695                const struct word_info *info,
00696                const struct search_settings *settings,
00697                struct search_info *searchinfo)
00698 {
00699     int have_match;
00700     regmatch_t re_match;
00701     size_t from_pos = 0;
00702     Boolean retval = True;
00703 
00704 #if SWITCH_TO_UTF8
00705     /* switch to UTF-8 encoding, as for regcomp() */
00706     char *utf8_locale = locale_to_utf8(globals.orig_locale);
00707     TRACE_FIND((stderr, "current locale: |%s|, utf8 version: |%s|", globals.orig_locale, utf8_locale));
00708     setlocale(LC_ALL, utf8_locale);
00709 #endif
00710 
00711     re_match.rm_so = re_match.rm_eo = -1;
00712     
00713     if (searchinfo->from_pos != -1)
00714        from_pos = searchinfo->from_pos;
00715     
00716     if (settings->direction == SEARCH_DOWN) {
00717        Boolean search_again = True;
00718        
00719        while (search_again && from_pos < info->curr_buf_idx) {
00720            searchinfo->have_match = False;
00721            search_again = False;
00722 
00723 /*         TRACE_FIND((stderr, "search string: |%s|, from_pos: %d", info->txt_buf + from_pos, from_pos)); */
00724            have_match = regexec(regex, info->txt_buf + from_pos, 1, &re_match, 0);
00725 
00726            if (have_match == 0) { /* match */
00727               TRACE_FIND((stderr, "match from %d to %d", (int)re_match.rm_so, (int)re_match.rm_eo));
00728               if (re_match.rm_so == re_match.rm_eo) { /* skip zero-length matches */
00729                   from_pos += re_match.rm_so + 1;
00730                   search_again = True;
00731               }
00732               else {
00733                   searchinfo->from_pos = re_match.rm_so + from_pos;
00734                   searchinfo->to_pos = re_match.rm_eo + from_pos;
00735                   searchinfo->have_match = True;
00736               }
00737            }
00738            else if (have_match != REG_NOMATCH) { /* error */
00739               report_regexp_error(have_match, regex, settings->term, 1);
00740               retval = False;
00741               break;
00742            }
00743        }
00744     }
00745     else { /* searching backwards */
00746        size_t curr_pos = 0, prev_pos = 0;
00747        regmatch_t re_match_bak;
00748        Boolean search_again = True;
00749        searchinfo->have_match = False;
00750        TRACE_FIND((stderr, "UP; trying to match |%s| from %lu, %lu",
00751                   settings->utf8_term,
00752                   (unsigned long)curr_pos, (unsigned long)from_pos));
00753 
00754        re_match_bak.rm_so = re_match_bak.rm_eo = -1;
00755        
00756        while (search_again && curr_pos <= from_pos) {
00757            search_again = False;
00758 
00759            /* remember previous match data */
00760            re_match_bak.rm_so = prev_pos + re_match.rm_so;
00761            re_match_bak.rm_eo = prev_pos + re_match.rm_eo;
00762 
00763            TRACE_FIND((stderr, "backup values: %d, %d; curr_pos: %lu",
00764                      (int)re_match_bak.rm_so, (int)re_match_bak.rm_eo, (unsigned long)curr_pos));
00765 
00766            /*            TRACE_FIND((stderr, "text buf: |%s|", info->txt_buf + curr_pos)); */
00767            have_match = regexec(regex, info->txt_buf + curr_pos, 1, &re_match, 0);
00768            while (have_match == 0 && re_match.rm_so == re_match.rm_eo) { /* skip zero-length matches */
00769               have_match = regexec(regex, info->txt_buf + ++curr_pos, 1, &re_match, 0);
00770            }
00771 
00772            
00773            if (have_match == 0) { /* match */
00774               TRACE_FIND((stderr, "match from %d to %d", (int)re_match.rm_so, (int)re_match.rm_eo));
00775               TRACE_FIND((stderr, "updating pos: %lu -> %lu; from_pos: %lu",
00776                          (unsigned long)curr_pos,
00777                          (unsigned long)(curr_pos + re_match.rm_eo),
00778                          (unsigned long)from_pos));
00779               prev_pos = curr_pos;
00780               curr_pos += re_match.rm_eo;
00781               search_again = True;
00782            }
00783            else if (have_match != REG_NOMATCH) { /* error */
00784               report_regexp_error(have_match, regex, settings->term, 1);
00785               retval = False;
00786               break;
00787            }
00788        }
00789        if (retval) {
00790            re_match.rm_so = re_match_bak.rm_so;
00791            re_match.rm_eo = re_match_bak.rm_eo;
00792            
00793            if (re_match.rm_so != -1) {
00794               TRACE_FIND((stderr, "remembering from-to: %d - %d", (int)re_match.rm_so, (int)re_match.rm_eo));
00795               searchinfo->from_pos = re_match.rm_so;
00796               searchinfo->to_pos = re_match.rm_eo;
00797               searchinfo->have_match = True;
00798            }
00799        }
00800     }
00801 
00802 #if SWITCH_TO_UTF8
00803     /* switch back to original encoding */
00804     setlocale(LC_ALL, globals.orig_locale);
00805     setlocale(LC_NUMERIC, "C");
00806     free(utf8_locale);
00807 #endif
00808     
00809     return retval;
00810 }
00811 #endif /* HAVE_REGEX_H */
00812 
00813 /* Erase exising highlighting. TODO: Move this into dvi-draw so that
00814    there's no big delay between erasing and drawing the next marker.
00815 */
00816 static void
00817 erase_match_highlighting(struct word_info *info, Boolean flag)
00818 {
00819     size_t i;
00820 
00821     if (info == NULL || (MAGNIFIER_ACTIVE && !INSIDE_MANE_WIN))
00822        return;
00823 
00824     for (i = 0; info->bboxes != NULL && i <= info->bboxes_idx && info->bboxes[i].ulx < INT_MAX; i++) {
00825        int x, y, w, h;
00826        GET_BBOX(info->bboxes[i], currwin.shrinkfactor, x, y, w, h);
00827        if (resource.match_highlight_inverted) {
00828            if (flag || m_highlight_region_changed) {
00829               clearexpose(&mane, x, y, w, h);
00830            }
00831            else if (clip_region(&x, &y, &w, &h)) {
00832               TRACE_FIND((stderr, "ERASING box: x %d, y %d, w %d, h %d",
00833                          x, y, w, h));
00834               TEST_DELAY("Showing BBOX ............... ");
00835               XClearArea(DISP, mane.win, x, y, w, h, False);
00836               TEST_DELAY("Clearing BBOX ............... ");
00837            }
00838        }
00839        else if (flag) {
00840            /* erase only the existing marks, not the entire bounding box, to avoid flashing of
00841               the text inside the bounding box. 1 pixel added to w, h just to make sure in case of
00842               rounding errors. */
00843            h++;
00844            clearexpose(&mane, x - 1, y - 1, 2, h + 2);
00845            clearexpose(&mane, x - 1, y - 1, w + 2, 2);
00846            clearexpose(&mane, x + w - 1, y - 1, 2, h + 2);
00847            clearexpose(&mane, x - 1, y + h - 1, w + 2, 2);
00848        }
00849     }
00850     if (flag) {
00851        reset_info(info);
00852        info = NULL;
00853     }
00854     else
00855        m_highlight_region_changed = False;
00856 }
00857 
00858 static void
00859 highlight_match(struct search_settings *settings,
00860               const struct word_info *w_info,
00861               const struct page_mapping *page_mapping)
00862 {
00863     const int context = 10;
00864     int match_page, i, j = 0;
00865     static struct word_info curr_info = { NULL, 0, 0, NULL, 0, 0, NULL, NULL, 0, True, False, False };
00866     struct search_info *searchinfo = settings->searchinfo;
00867     Boolean match_wrapped = False;
00868     
00869     curr_info.settings = settings;
00870     curr_info.page_mapping = page_mapping;
00871 
00872     TRACE_FIND((stderr, "MATCH from pos %d to %d:",
00873               searchinfo->from_pos, searchinfo->to_pos));
00874     
00875     if (globals.debug & DBG_FIND) {
00876        fprintf(stderr, "current page_mapping:\n");
00877        for (i = 0; i < 2; i++) {
00878            fprintf(stderr, "%d: %d\n", page_mapping[i].pageno, page_mapping[i].offset);
00879        }
00880     }
00881 
00882     TRACE_FIND((stderr, "to_pos: %d, %d", searchinfo->to_pos, page_mapping[0].offset));
00883     if (searchinfo->from_pos < page_mapping[0].offset) {
00884        match_page = page_mapping[0].pageno;
00885        if (searchinfo->to_pos > page_mapping[0].offset) {
00886            match_wrapped = True;
00887        }
00888     }
00889     else {
00890        ASSERT(searchinfo->from_pos < page_mapping[1].offset, "to_pos should be smaller than page_mapping[1].offset!");
00891        match_page = page_mapping[1].pageno;
00892        if (searchinfo->to_pos > page_mapping[1].offset) {
00893            match_wrapped = True;
00894        }
00895     }
00896     if (match_wrapped)
00897        statusline_print(STATUS_MEDIUM, "Match from page %d to %d", match_page + 1,  match_page + 2);
00898     else
00899        statusline_print(STATUS_MEDIUM, "Match on page %d", match_page + 1);
00900     
00901     if (globals.debug & DBG_FIND) {
00902        fprintf(stderr, "*** match_page: %d, adding: %d\n", match_page, searchinfo->page_offset);
00903     
00904        for (j = searchinfo->from_pos - context; j < searchinfo->from_pos; j++) {
00905            if (j >= 0)
00906               fputc(w_info->txt_buf[j], stderr);
00907            else
00908               fputc(' ', stderr);
00909        }
00910        fputs("\n        >>", stderr);
00911        for (j = searchinfo->from_pos; j < searchinfo->to_pos; j++) {
00912            fputc(w_info->txt_buf[j], stderr);
00913        }
00914        fputs("<<\n          ", stderr);
00915        for (j = searchinfo->from_pos; j < searchinfo->to_pos; j++) {
00916            fputc(' ', stderr);
00917        }
00918        for (j = searchinfo->to_pos; j < (int)w_info->curr_buf_idx; j++) {
00919            fputc(w_info->txt_buf[j], stderr);
00920            if (w_info->txt_buf[j] == '\n')
00921               break;
00922        }
00923        fputc('\n', stderr);
00924     }
00925     
00926     if (match_page != current_page) {
00927        goto_page(match_page, resource.keep_flag ? NULL : home, False);
00928        page_history_insert(match_page);
00929 /*     globals.ev.flags |= EV_NEWPAGE; */
00930     }
00931     /* don't raise the window - this can obscure the search popup */
00932     /*     XMapRaised(DISP, XtWindow(globals.widgets.top_level)); */
00933 
00934     /* this erases contents of m_info, so be careful - contents of curr_info
00935        will be indirectly erased by this ... */
00936     erase_match_highlighting(m_info, True);
00937     
00938     /* now rescan the page to get bounding box info for the match */
00939     scan_page(globals.dvi_file.bak_fp, match_page, &curr_info);
00940     m_info = &curr_info;
00941     
00942     m_match_page = match_page;
00943     
00944     do_autoscroll = True; /* enable scrolling to match */
00945     /* create an expose event so that the region gets highlighted */
00946     if (m_info != NULL) {
00947        generate_highlight_expose(m_info);
00948     }
00949 }
00950 
00951 #if HAVE_REGEX_H
00952 
00953 /*
00954  * A mapping of Perl-style character classes to POSIX-style character classes.
00955  */
00956 static struct perl2posix_mapping {
00957     const char *perl_class;
00958     const char *posix_class;
00959 } perl2posix_map[] = {
00960     { "\\w", "[[:alnum:]]" },
00961     { "\\W", "[^[:alnum:]]" },
00962     { "\\d", "[[:digit:]]" },
00963     { "\\D", "[^[:digit:]]" },
00964     { "\\s", "[[:space:]]" },
00965     { "\\S", "[^[:space:]]" },
00966     { NULL, NULL } /* terminate */
00967 };
00968 
00969 /*
00970  * Convert `perl_regex', a regexp using Perl-style character classes,
00971  * to a regexp using POSIX-style character classes. Return the latter
00972  * in freshly allocated memory, which the caller is responsible to free().
00973  * Uses the perl2posix_map table.
00974  */
00975 static char *
00976 perl2posix(const char *perl_regex)
00977 {
00978     char *posix_regex = xstrdup(perl_regex);
00979     size_t i;
00980 
00981     for (i = 0; perl2posix_map[i].perl_class != NULL; i++) {
00982        const char *ptr;
00983        size_t offset = 0;
00984        TRACE_FIND((stderr, "searching for |%s| at offset %lu", perl2posix_map[i].perl_class, (unsigned long)offset));
00985        while ((ptr = find_format_str(posix_regex + offset, perl2posix_map[i].perl_class)) != NULL) {
00986            size_t len1 = ptr - posix_regex; /* length up to match */
00987            size_t len2 = strlen(perl2posix_map[i].posix_class);
00988            TRACE_FIND((stderr, "Regexp: mapping %s to %s",
00989                      perl2posix_map[i].perl_class,
00990                      perl2posix_map[i].posix_class));
00991            /* -1 since -2 for perl format string, +1 for trailing '\0' */
00992            posix_regex = xrealloc(posix_regex, strlen(posix_regex) + len2 - 1);
00993            /* make space for new format string */
00994            memmove(posix_regex + len1 + len2, posix_regex + len1 + 2, strlen(posix_regex + len1 + 2) + 1);
00995            /* copy in new format string */
00996            memcpy(posix_regex + len1, perl2posix_map[i].posix_class, len2);
00997            TRACE_FIND((stderr, "Expanded regex: |%s|", posix_regex));
00998            offset = len1 + len2;
00999            TRACE_FIND((stderr, "searching again for |%s| at offset %lu",
01000                      perl2posix_map[i].perl_class,
01001                      (unsigned long)offset));
01002        }
01003     }
01004     return posix_regex;
01005 }
01006 
01007 #endif /* HAVE_REGEX_H */
01008 
01009 char *
01010 get_text_selection(int *len, int ulx, int uly, int lrx, int lry)
01011 {
01012     struct word_info txt_info = { NULL, 0, 0, NULL, 0, 0, NULL, NULL, 0, False, False, True };
01013     struct bbox text_bbox;
01014     
01015     text_bbox.ulx = ulx;
01016     text_bbox.uly = uly;
01017     text_bbox.lrx = lrx;
01018     text_bbox.lry = lry;
01019 
01020     txt_info.bboxes = &text_bbox;
01021 
01022     /* initialize buffer with empty string */
01023     txt_info.txt_buf_size = 1;
01024     txt_info.txt_buf = xmalloc(txt_info.txt_buf_size);
01025     txt_info.txt_buf[0] = '\0';
01026 
01027     /* this enlarges the buffer as needed */
01028     scan_page(globals.dvi_file.bak_fp, current_page, &txt_info);
01029     
01030     *len = txt_info.txt_buf_size;
01031     return txt_info.txt_buf;
01032     
01033 #if 0
01034     /*     fprintf(stderr, "========== SELECTION (len %d):\n", txt_info.curr_buf_idx); */
01035     /*     dump_buffer(&txt_info, 0, stderr, FMT_ISO_8859_1); */
01036     /*     fprintf(stderr, "==========\n"); */
01037 
01038     buf = xmalloc(4 * txt_info.curr_buf_idx + 1); /* just in case we get many non-printables */
01039     while (i < txt_info.curr_buf_idx) {
01040        uint32_t ucs4;
01041        const char *ret;
01042        
01043        /* first apply normalization heurisitcs also used by search */
01044        size_t len = utf8_to_ucs4(txt_info.txt_buf + i, &ucs4, strlen(txt_info.txt_buf + i));
01045        if ((ret = search_normalize_chars(ucs4)) != NULL) {
01046            size_t len_ret = strlen(ret);
01047            memcpy(buf + offset, ret, len_ret);
01048            offset += len_ret;
01049        }      
01050        else if (ucs4 <= 0xff) { /* in iso-latin1 range */
01051            buf[offset++] = (unsigned char)ucs4;
01052        }
01053        else {
01054            sprintf(buf + offset, "\\%.4lX", ucs4);
01055            offset += 4;
01056        }
01057        i += len;
01058     }
01059     buf[offset] = '\0';
01060     free(txt_info.txt_buf);
01061     return buf;
01062 #endif /* 0 */
01063 }
01064 
01065 Boolean
01066 search_extract_text(outputFormatT fmt, struct select_pages_info *pinfo)
01067 {
01068     int i;
01069     FILE *fp;
01070     struct file_info *finfo = pinfo->finfo;
01071     
01072     if ((fp = XFOPEN(finfo->txt_out.fname, "wb")) == NULL) {
01073        popup_message(globals.widgets.top_level,
01074                     MSG_ERR,
01075                     NULL, "Could not open %s for writing: %s.",
01076                     finfo->txt_out.fname,
01077                     strerror(errno));
01078        return False;
01079     }
01080 
01081     for (i = 0; i < total_pages; i++) {
01082        if (pinfo->callback == NULL || pinfo->callback(pinfo, i)) {
01083            struct word_info txt_info = { NULL, 0, 0, NULL, 0, 0, NULL, NULL, 0, False, False, False };
01084            scan_page(globals.dvi_file.bak_fp, i, &txt_info);
01085 
01086            dump_buffer(&txt_info, 0, fp, fmt);
01087 
01088            free(txt_info.txt_buf);
01089            
01090            fputs("\n\n", fp); /* two newlines for page breaks */
01091        }
01092     }
01093     
01094     fclose(fp);
01095     return True;
01096 }
01097 
01098 void
01099 search_erase_highlighting(Boolean reset)
01100 {
01101     erase_match_highlighting(m_info, reset);
01102 }
01103 
01104 static void
01105 message_search_ended(XtPointer arg)
01106 {
01107     struct search_settings *settings = (struct search_settings *)arg;
01108 
01109     settings->searchinfo->from_pos = settings->searchinfo->to_pos = -1;
01110     TRACE_FIND((stderr, "search ended; current_page: %d", current_page));
01111     settings->from_page = current_page;
01112     search_signal_page_changed();
01113     settings->message_window = 0;
01114     /*     statusline_append(STATUS_SHORT, " stopped."); */
01115     statusline_print(STATUS_SHORT, "Search stopped.");
01116 }
01117 
01118 
01119 void
01120 search_restart(XtPointer arg)
01121 {
01122     struct search_settings *settings = (struct search_settings *)arg;
01123     Widget popup, textfield;
01124     char *searchterm = NULL;
01125     
01126     TRACE_FIND((stderr, "restart search!"));
01127 
01128     /* Update the search term (user might have changed it before
01129        hitting `Return' to restart the search).  We need to start
01130        from the toplevel widget since this method may be called
01131        from a different window (e.g. confirmation popup).
01132     */
01133     if (get_widget_by_name(&popup, globals.widgets.top_level, Xdvi_SEARCH_POPUP_NAME, True)
01134        && get_widget_by_name(&textfield, popup, Xdvi_SEARCHBOX_INPUT_NAME, True)) {
01135        XtVaGetValues(textfield,
01136 #ifdef MOTIF
01137                     XmNvalue,
01138 #else
01139                     XtNstring,
01140 #endif
01141                     &searchterm, NULL);
01142        if (searchterm == NULL) {
01143            XDVI_WARNING((stderr, "Got NULL searchterm in search_restart()!"));
01144            return;
01145        }
01146        settings->term = searchterm;
01147     }
01148 
01149     if (settings->direction == SEARCH_DOWN) {
01150        settings->from_page = 0;
01151     }
01152     else {
01153        settings->from_page = total_pages - 1;
01154     }
01155     if (settings->direction == SEARCH_DOWN)
01156        settings->searchinfo->from_pos = settings->searchinfo->to_pos = -1;
01157     else
01158        settings->searchinfo->from_pos = settings->searchinfo->to_pos = INT_MAX;
01159     
01160     settings->message_window = 0;
01161     search_signal_page_changed();
01162     search_dvi((XtPointer)settings);
01163 }
01164 
01165 
01166 static void
01167 normalize_newline(char *str)
01168 {
01169     size_t i, j;
01170     for (i = 0, j = 0; str[i] != '\0'; i++, j++) {
01171        if (str[i] == '\\' && str[i + 1] == 'n') {
01172            if (i > 0 && str[i - 1] == '\\') {
01173               str[j] = 'n';
01174               i++;
01175            }
01176            else {
01177               str[j] = '\n';
01178               i++;
01179            }
01180        }
01181        else {
01182            str[j] = str[i];
01183        }
01184     }
01185     str[j] = str[i]; /* copy terminating '\0' */
01186 }
01187 
01188 
01189 static Boolean
01190 reinit_searchterm(struct search_settings *settings, const char *encoding)
01191 {
01192     struct search_info *searchinfo = settings->searchinfo;
01193 
01194     free(settings->utf8_term);
01195     settings->utf8_term = NULL;
01196 
01197     if (memicmp(encoding, "iso-8859-1", strlen("iso-8859-1")) == 0
01198        || memicmp(encoding, "iso8859-1", strlen("iso8859-1")) == 0) {
01199        int conv_len = (strlen(settings->term) + 1) * 2;
01200        int ret_len;
01201        char *ptr = xmalloc(conv_len);
01202        if ((ret_len = str_iso_8859_1_to_utf8(settings->term, ptr, conv_len)) < 0) {
01203            XBell(DISP, 0);
01204            popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"),
01205                        MSG_ERR,
01206                        NULL,
01207                        "Shouldn't happen: search term `%s' is too long (> %d)", settings->term, conv_len);
01208            searchinfo->locked = False;
01209            free(ptr);
01210            settings->utf8_term = xstrdup("");
01211            return False;
01212        }
01213        settings->utf8_term = ptr;
01214     }
01215     else if (memicmp(encoding, "utf-8", strlen("utf-8")) == 0
01216             || memicmp(encoding, "utf8", strlen("utf8")) == 0) {
01217        settings->utf8_term = xstrdup(settings->term);
01218     }
01219     else {
01220        if ((settings->utf8_term = iconv_convert_string(encoding, "utf-8", settings->term)) == NULL) {
01221            return False;
01222        }
01223     }
01224 
01225     TRACE_FIND((stderr, "UTF-8 search term: |%s|", settings->utf8_term));
01226     normalize_newline(settings->utf8_term);
01227     TRACE_FIND((stderr, "UTF-8 search term after normalizing newline: |%s|", settings->utf8_term));
01228 
01229 #if 0
01230     /* lowercasing for regexps is dealt with by REG_ICASE */
01231     if (!settings->case_sensitive && !settings->use_regexp) {
01232         if (!utf8_lowercase(settings->utf8_term))
01233             return False;
01234         TRACE_FIND((stderr, "Lowercased UTF-8 search term: |%s|", settings->utf8_term));
01235     }
01236 #else /* always lowercase, since REG_ICASE is broken with UTF-8(?) */
01237     if (!settings->case_sensitive && !utf8_lowercase(settings->utf8_term))
01238        return False;
01239     TRACE_FIND((stderr, "Lowercased UTF-8 search term: |%s|", settings->utf8_term));
01240 #endif /* 1 */
01241     
01242     /* remove spaces/newlines before/after an ideographic char */
01243     {
01244        unsigned char *p = (unsigned char *)settings->utf8_term;
01245        unsigned char *q;
01246        int l, len = strlen((char *)p);
01247        uint32_t ucs4;
01248        Boolean had_ideograph;
01249        
01250        had_ideograph = False;
01251        while (len > 0) {
01252            if (*p == ' ' || *p == '\n') {
01253               q = p + 1;
01254               while (*q == ' ' || *q == '\n')
01255                   q++;
01256               len -= q - p;
01257               if (had_ideograph || is_utf8_ideograph(q))
01258                   memmove(p, q, len + 1); /* remove spaces/newlines */
01259               else
01260                   p = q;                  /* preserve spaces/newlines */
01261            }
01262            else {
01263               if ((l = utf8_to_ucs4((char *)p, &ucs4, len)) <= 0)
01264                   break;
01265               len -= l;
01266               had_ideograph = is_ideograph(ucs4);
01267               p += l;
01268            }
01269        }
01270 
01271        settings->utf8_term = xrealloc(settings->utf8_term, strlen(settings->utf8_term) + 1);
01272     }
01273     
01274     return True;
01275 }
01276 
01277 void
01278 search_reset_info(void)
01279 {
01280     TRACE_FIND((stderr, "resetting info!"));
01281     reset_info(m_info);
01282     m_info = NULL;
01283 }
01284 
01285 
01286 #if HAVE_REGEX_H
01287 static Boolean
01288 do_recompile_regexp(struct search_settings *settings,
01289                   regex_t *regex)
01290 {
01291     int re_retval;
01292     /* use extended POSIX, and make `.' not match newlines (otherwise it's
01293        often too unintuitive because of the greediness of '+'/'*' */
01294     int re_flags = REG_EXTENDED | REG_NEWLINE;
01295     TRACE_FIND((stderr, "compiling regexp ..."));
01296     
01297     if (settings->posix_term != NULL) {
01298        TRACE_FIND((stderr, "freeing old regexp ..."));
01299        free(settings->posix_term);
01300        regfree(regex);
01301     }
01302     settings->posix_term = perl2posix(settings->utf8_term);
01303 
01304 #if 0 /* this is broken with e.g. german umlauts; maybe it can't deal with UTF-8 encoding? */
01305     /* compile regexp from search term */
01306     if (!settings->case_sensitive)
01307         re_flags |= REG_ICASE;
01308 #endif
01309     
01310     { /* change the encoding part of the locale to UTF-8 to match encoding
01311         of the search string and the search buffer */
01312 #if SWITCH_TO_UTF8
01313        char *utf8_locale = locale_to_utf8(globals.orig_locale);
01314        TRACE_FIND((stderr, "current locale: |%s|, utf8 version: |%s|", globals.orig_locale, utf8_locale));
01315         setlocale(LC_ALL, utf8_locale);
01316 #endif
01317         re_retval = regcomp(regex, settings->posix_term, re_flags);
01318 #if SWITCH_TO_UTF8
01319         setlocale(LC_ALL, globals.orig_locale);
01320         setlocale(LC_NUMERIC, "C");
01321        free(utf8_locale);
01322 #endif
01323     }
01324 
01325     if (re_retval != 0) {
01326        report_regexp_error(re_retval, regex, settings->term, 0);
01327        settings->searchinfo->locked = False;
01328        return False;
01329     }
01330 
01331     return True;
01332 }
01333 
01334 #else  /* HAVE_REGEX_H */
01335 
01336 static void
01337 warn_no_regex(void)
01338 {
01339     XBell(DISP, 0);
01340     popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"),
01341                 MSG_WARN,
01342                 NULL,
01343                 "POSIX regular expression support (regex.h) is not available on this platform; "
01344                 "using ordinary string matching instead.");
01345 }
01346 #endif /* HAVE_REGEX_H */
01347 
01348 /*
01349   Scan at most two adjacent pages, writing results in to page_mapping.
01350   Return False if user cancelled the search, True else.
01351 */
01352 static Boolean
01353 scan_two_pages(struct search_settings *settings,
01354               struct word_info *info,
01355               struct page_mapping *page_mapping,
01356               int curr_page)
01357 {
01358     Boolean cancelled = False;
01359     struct search_info *searchinfo = settings->searchinfo;
01360     
01361     if (curr_page != page_mapping[0].pageno
01362        && curr_page != page_mapping[1].pageno) { /* none of 2 pages scanned yet */
01363        reset_info(info);
01364        if (settings->direction == SEARCH_DOWN) {
01365            if (!do_scan_page(info, settings, &(page_mapping[0]),
01366                            0, curr_page, settings->direction)
01367               || (curr_page + 1 < total_pages &&
01368                   !do_scan_page(info, settings, &(page_mapping[1]),
01369                               page_mapping[0].offset, curr_page + 1, settings->direction)))
01370               cancelled = True;
01371        }
01372        else {
01373            if (!do_scan_page(info, settings, &(page_mapping[1]),
01374                            0, curr_page, settings->direction)
01375               || (curr_page > 0 &&
01376                   !do_scan_page(info, settings, &(page_mapping[0]),
01377                               page_mapping[1].offset, curr_page - 1, settings->direction)))
01378               cancelled = True;
01379            else if (page_mapping[0].offset != -1) {
01380               page_mapping[1].offset += page_mapping[0].offset;
01381               if (searchinfo->from_pos != INT_MAX)
01382                   searchinfo->from_pos += page_mapping[0].offset;
01383            }
01384        }
01385     }
01386     else if (curr_page != page_mapping[0].pageno) { /* current page scanned as page_mapping[1] */
01387        if (settings->direction == SEARCH_DOWN && curr_page + 1 < total_pages) {
01388            shift_info_down(searchinfo, info, page_mapping);
01389            if (!do_scan_page(info, settings, &(page_mapping[1]),
01390                            page_mapping[0].offset, curr_page + 1, settings->direction))
01391               cancelled = True;
01392        }
01393        else if (curr_page - 1 != page_mapping[0].pageno && curr_page > 0) {
01394            if (!do_scan_page(info, settings, &(page_mapping[0]),
01395                            0, curr_page - 1, settings->direction))
01396               cancelled = True;
01397            else {
01398               page_mapping[1].offset += page_mapping[0].offset;
01399               if (searchinfo->from_pos != INT_MAX)
01400                   searchinfo->from_pos += page_mapping[0].offset;
01401            }
01402        }
01403     }
01404     else if (curr_page != page_mapping[1].pageno) { /* current page scanned as page_mapping[0] */
01405        if (settings->direction == SEARCH_UP && curr_page > 0) {
01406            shift_info_up(info, page_mapping);
01407            if (!do_scan_page(info, settings, &(page_mapping[0]),
01408                            0, curr_page - 1, settings->direction))
01409               cancelled = True;
01410            else {
01411               page_mapping[1].offset += page_mapping[0].offset;
01412               if (searchinfo->from_pos != INT_MAX) {
01413                   searchinfo->from_pos += page_mapping[0].offset;
01414                   searchinfo->to_pos += page_mapping[0].offset;
01415               }
01416               TRACE_FIND((stderr, "new offsets: from=%d, to=%d", searchinfo->from_pos, searchinfo->to_pos));
01417            }
01418        }
01419        else if (curr_page + 1 != page_mapping[1].pageno && curr_page + 1 < total_pages) {
01420            if (!do_scan_page(info, settings, &(page_mapping[1]),
01421                            page_mapping[0].offset, curr_page + 1, settings->direction))
01422               cancelled = True;
01423        }
01424     }
01425     
01426     return !cancelled;
01427 }
01428 
01429 void
01430 search_dvi(XtPointer arg)
01431 {
01432     struct search_settings *settings = (struct search_settings *)arg;
01433     struct search_info *searchinfo = settings->searchinfo;
01434     
01435 #if HAVE_REGEX_H
01436     static regex_t regex;
01437 #endif /* HAVE_REGEX_H */
01438     
01439     /* a mapping of page numbers to index positions in w_info->txt_buf,
01440        for the 2 pages that we scanned */
01441     static struct page_mapping page_mapping[2] = { { -1, -1 }, { -1, -1 } };
01442     
01443     static struct word_info w_info = { NULL, 0, 0, NULL, 0, 0, NULL, NULL, 0, False, False, False };
01444 
01445     static time_t dvi_time_bak = 0;
01446     static char *searchterm_bak = NULL;
01447 
01448     static const char *text_encoding = NULL;
01449 
01450     static Boolean case_sensitive_bak = False;
01451     static Boolean ignore_hyphens_bak = False;
01452     static Boolean ignore_linebreaks_bak = False;
01453     static searchDirectionT direction_bak = SEARCH_UNINITIALIZED;
01454     /* from_pos_bak is needed to mark the start of the match when switching from
01455        search down to search backwards, where we want to jump to the previous match,
01456        so we need the start of the current match again (and down search sets
01457        searchinfo->from_pos = searchinfo->to_pos) */
01458     static int from_pos_bak = -1;
01459     int curr_page = settings->from_page;
01460 
01461     Boolean reinit = False;
01462     Boolean recompile_regexp = False;
01463     Boolean adjust_hyphen_offset = False;
01464     
01465     /* prevent multiple invocations while still busy searching */
01466     if (searchinfo->locked) {
01467        TRACE_FIND((stderr, "LOCKED"));
01468        return;
01469     }
01470     searchinfo->locked = True;
01471 
01472     if (dvi_time_bak == 0) { /* first invocation */
01473        case_sensitive_bak = settings->case_sensitive;
01474        ignore_hyphens_bak = settings->ignore_hyphens;
01475        ignore_linebreaks_bak = settings->ignore_linebreaks;
01476        dvi_time_bak = globals.dvi_file.time;
01477     }
01478     if (globals.dvi_file.time > dvi_time_bak) {
01479        dvi_time_bak = globals.dvi_file.time;
01480        reinit = True;
01481     }
01482 
01483     m_match_page = -1;
01484     
01485     if (raise_message_windows()) { /* any popups user still needs to deal with? */
01486        TRACE_GUI((stderr, "Still open message windows to deal with, returning ..."));
01487        searchinfo->locked = False;
01488        return;
01489     }
01490 
01491     ASSERT(settings->term != NULL, "settings->term mustn't be NULL!");
01492     if (strlen(settings->term) == 0) {
01493        positioned_popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"),
01494                              MSG_ERR,
01495                              settings->x_pos, settings->y_pos,
01496                              NULL, "Empty search term");
01497        searchinfo->locked = False;
01498        return;
01499     }
01500     
01501     TRACE_FIND((stderr, "dvi_search: Searching for |%s| from page %d", settings->term, settings->from_page));
01502     TRACE_FIND((stderr, "  settings: down = %d, re = %d, case = %d",
01503               settings->direction, settings->use_regexp, settings->case_sensitive));
01504 
01505     if (m_changed_page) {
01506        m_changed_page = False;
01507        reinit = True;
01508     }
01509     
01510     /* initialize text_encoding */
01511     if (text_encoding == NULL) {
01512        text_encoding = get_text_encoding();
01513     }
01514     
01515     /* first call to this routine, or search term changed:
01516        Re-initialize the utf-8 representation and the regexp */
01517     if (settings->utf8_term == NULL
01518        || searchterm_bak == NULL
01519        || strcmp(settings->term, searchterm_bak) != 0) {
01520 
01521        if (!reinit_searchterm(settings, text_encoding)) {
01522            searchinfo->locked = False;
01523            return;
01524        }
01525 
01526        free(searchterm_bak);
01527        searchterm_bak = xstrdup(settings->term);
01528        recompile_regexp = True;
01529     }
01530 
01531     if (strlen(settings->utf8_term) == 0) {
01532        positioned_popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"),
01533                              MSG_WARN,
01534                              settings->x_pos, settings->y_pos,
01535                              NULL, "Search term is empty after UTF-8 conversion!");
01536        searchinfo->locked = False;
01537        return;
01538     }
01539 
01540     if (direction_bak != settings->direction && direction_bak != SEARCH_UNINITIALIZED
01541        && searchinfo->from_pos != INT_MAX && searchinfo->from_pos != -1) {
01542        TRACE_FIND((stderr, "changed direction! from_pos: %d", searchinfo->from_pos));
01543        if (settings->direction == SEARCH_DOWN) {
01544            searchinfo->from_pos = searchinfo->to_pos;
01545            TRACE_FIND((stderr, "DOWN; new from_pos: %d", searchinfo->from_pos));
01546        }
01547        else if (settings->direction == SEARCH_UP) {
01548            if (from_pos_bak != -1) {
01549               searchinfo->from_pos = from_pos_bak;
01550            }
01551            else {
01552               searchinfo->from_pos = 0;
01553            }
01554            TRACE_FIND((stderr, "UP; new from_pos: %d", searchinfo->from_pos));
01555        }
01556     }
01557     direction_bak = settings->direction;
01558 
01559     /* If one of the settings for case, hyphens ore linebreaks has
01560      * changed, we need to rescan the page to undo lowercasing,
01561      * hyphenation or linebreak removal in w_info, but preserve
01562      * searchinfo->from_pos since user will want to find next match.
01563      */
01564     if (case_sensitive_bak != settings->case_sensitive
01565        || ignore_hyphens_bak != settings->ignore_hyphens
01566        || ignore_linebreaks_bak != settings->ignore_linebreaks) {
01567 
01568        if (ignore_hyphens_bak != settings->ignore_hyphens)
01569            adjust_hyphen_offset = True;
01570        case_sensitive_bak = settings->case_sensitive;
01571        ignore_hyphens_bak = settings->ignore_hyphens;
01572        ignore_linebreaks_bak = settings->ignore_linebreaks;
01573        
01574        reset_info(&w_info);
01575 
01576        /* adjust from_pos if it's on second page (we'll rescan it as first page) */
01577        if (searchinfo->from_pos >= page_mapping[0].offset)
01578            searchinfo->from_pos -= page_mapping[0].offset;
01579 
01580        TRACE_FIND((stderr, "from_pos: %d", searchinfo->from_pos));
01581        page_mapping[0].offset = page_mapping[1].offset = page_mapping[0].pageno = page_mapping[1].pageno = -1;
01582        /* also need to recompile regexp, and undo lowercasing of search term */
01583        if (!reinit_searchterm(settings, text_encoding))
01584            return;
01585        recompile_regexp = True;
01586     }
01587     
01588     if (reinit) { /* file changed, or on different page: re-initialize scan info */
01589        TRACE_FIND((stderr, "re-initializing scan info!"));
01590        page_mapping[0].offset = page_mapping[1].offset = page_mapping[0].pageno = page_mapping[1].pageno = -1;
01591 
01592        /* re-initialize info */
01593        reset_info(&w_info);
01594        if (settings->direction == SEARCH_DOWN)
01595            searchinfo->from_pos = searchinfo->to_pos = 0;
01596        else
01597            settings->searchinfo->from_pos = settings->searchinfo->to_pos = INT_MAX;
01598     }
01599 
01600     TRACE_FIND((stderr, "from_pos initialized with: %d", settings->searchinfo->from_pos));
01601     
01602     if (settings->use_regexp && recompile_regexp) {
01603 #if HAVE_REGEX_H
01604        if (!do_recompile_regexp(settings, &regex))
01605            return;
01606 #else
01607        warn_no_regex();
01608 #endif /* HAVE_REGEX_H */
01609     }
01610 
01611     for (;;) {       /* scan pages, try to match */
01612        statusline_print(STATUS_MEDIUM, "Searching on page %d", curr_page);
01613        TRACE_FIND((stderr, "curr_page: %d, pageno 0: %d, pageno1: %d",
01614                   curr_page, page_mapping[0].pageno, page_mapping[1].pageno));
01615 
01616        settings->hyphen_delta = 0;
01617        /* scan_two_pages will return False if user cancelled */
01618        if (!scan_two_pages(settings, &w_info, page_mapping, curr_page)
01619            || (read_events(EV_NOWAIT) & EV_GE_FIND_CANCEL)) {
01620            statusline_append(STATUS_SHORT,
01621                            "- cancelled.",
01622                            "- cancelled.");
01623            settings->searchinfo->locked = False;
01624            return;
01625        
01626        }
01627 
01628        TRACE_FIND((stderr, "page mapping:\n%d: %d\n%d: %d",
01629                   page_mapping[0].pageno, page_mapping[0].offset,
01630                   page_mapping[1].pageno, page_mapping[1].offset));
01631        
01632 /*     dump_buffer(&w_info, 0, stderr, FMT_ISO_8859_1); */
01633 
01634        /* If ignore_hyphens has changed (in which case adjust_hyphen_offset
01635           is true), the buffer will contain settings->hyphen_delta fewer
01636           or more characters than in the previous pass due to the removal
01637           or addition of hyphens; adjust from_pos and to_pos accordingly:
01638        */
01639        if (adjust_hyphen_offset) {
01640            TRACE_FIND((stderr, "adjusting offset by %d", settings->hyphen_delta));
01641            if (settings->ignore_hyphens) { /* fewer characters */
01642               settings->searchinfo->from_pos -= settings->hyphen_delta;
01643               settings->searchinfo->to_pos -= settings->hyphen_delta;
01644            }
01645            else { /* more characters */
01646               settings->searchinfo->from_pos += settings->hyphen_delta;
01647               settings->searchinfo->to_pos += settings->hyphen_delta;
01648            }
01649            TRACE_FIND((stderr, "NEW from_pos: %d; to_pos: %d",
01650                      settings->searchinfo->from_pos, settings->searchinfo->to_pos));
01651        }
01652 
01653        
01654        /* match the searchstring */
01655 #if HAVE_REGEX_H
01656        if (settings->use_regexp) {
01657            if (!try_regexp_match(&regex, &w_info, settings, searchinfo)) /* regexp error */
01658               return;
01659            
01660        }
01661        else {
01662 #endif
01663            try_match(&w_info, settings, searchinfo);
01664 #if HAVE_REGEX_H
01665        }
01666 #endif
01667 
01668        /* again, check if user cancelled */
01669        if (read_events(EV_NOWAIT) & EV_GE_FIND_CANCEL) { /* user cancelled */
01670            statusline_append(STATUS_SHORT,
01671                            "- cancelled.",
01672                            "- cancelled.");
01673            settings->searchinfo->locked = False;
01674            return;
01675        
01676        }
01677        
01678        if (searchinfo->have_match) { /* match, highlight it */
01679            highlight_match(settings, &w_info, page_mapping);
01680            settings->searchinfo->locked = False;
01681            if (settings->direction == SEARCH_DOWN) {
01682               from_pos_bak = searchinfo->from_pos;
01683               searchinfo->from_pos = searchinfo->to_pos;
01684            }
01685            break;
01686        }
01687        else if ((settings->direction == SEARCH_DOWN && curr_page + 1 < total_pages)
01688                || (settings->direction == SEARCH_UP && curr_page > 0)) {
01689            if (settings->direction == SEARCH_DOWN)
01690               curr_page++;
01691            else
01692               curr_page--;
01693            TRACE_FIND((stderr, "continuing on page %d", curr_page));
01694            erase_match_highlighting(m_info, True);
01695            /* no match, and we have more pages; continue scanning */
01696            continue;
01697        }
01698        else { /* reached end of file */
01699            Widget find_popup;
01700 
01701            if ((find_popup = XtNameToWidget(globals.widgets.top_level, "*find_popup")) == 0) {
01702               XDVI_WARNING((stderr, "Couldn't find \"find_popup\" widget!"));
01703               find_popup = globals.widgets.top_level;
01704            }
01705            
01706            statusline_append(STATUS_MEDIUM,
01707                            "... searched to end of file.",
01708                            "... searched to end of file.");
01709            erase_match_highlighting(m_info, True);
01710            settings->searchinfo->locked = False;
01711            
01712            settings->message_window =
01713               positioned_choice_dialog(find_popup,
01714                                     MSG_QUESTION,
01715                                     settings->x_pos, settings->y_pos,
01716                                     NULL,
01717 #ifndef MOTIF
01718                                     "do-search-restart",
01719 #endif
01720                                     NULL, NULL, /* no pre_callbacks */
01721                                     "Yes", search_restart, (XtPointer)settings,
01722                                     "Cancel", message_search_ended, (XtPointer)settings,
01723                                     "Searched %s of file without finding the pattern.\n"
01724                                     "Start again from the %s of the file?",
01725                                     settings->direction == SEARCH_DOWN
01726                                     ? "forward to end" : "backward to beginning",
01727                                     settings->direction == SEARCH_DOWN
01728                                     ? "beginning" : "end");
01729            TRACE_GUI((stderr, "message_window: %p\n", (void *)settings->message_window));
01730            /* notreached */
01731            break;
01732        }
01733     } /* for(;;) */
01734 }