/* * Copyright (c) 2003-2004 Stefan Ulrich * * 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 * PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE 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. */ #include "xdvi-config.h" #include "xdvi.h" #include "events.h" #include "dvi-init.h" #include "search-dialog.h" #include "search-internal.h" #include "xlwradio.h" #include "statusline.h" #include "string-utils.h" #include "string_list.h" #include "util.h" #include "message-window.h" #include "x_util.h" #include "xm_menu.h" /* for popdown_callback() */ #include #include #ifdef MOTIF # include # include # include # include # include # include # include # include # include # include # include # include # include # if USE_COMBOBOX # include # include # endif /* USE_COMBOBOX */ #else /* MOTIF */ # include # include # include # include # include # include # include # include #endif /* MOTIF */ /* #defines for widgets that are used in common code */ #ifdef MOTIF # define SHELL_WIDGET xmDialogShellWidgetClass # define PANED_WIDGET xmPanedWindowWidgetClass # define FORM_WIDGET xmFormWidgetClass # define LABEL_WIDGET xmLabelGadgetClass # define TEXT_WIDGET xmTextFieldWidgetClass # define CHECKBOX_WIDGET xmToggleButtonGadgetClass # define CHECKBUTTON_IS_SET XmNset # define PUSHBUTTON_WIDGET xmPushButtonWidgetClass # define VALUE_CALLBACK_NAME XmNvalueChangedCallback # define ACTIVATE_CALLBACK_NAME XmNactivateCallback #else /* MOTIF */ # define SHELL_WIDGET transientShellWidgetClass # define PANED_WIDGET panedWidgetClass # define FORM_WIDGET formWidgetClass # define LABEL_WIDGET labelWidgetClass # define TEXT_WIDGET asciiTextWidgetClass # ifdef XAW # define CHECKBOX_WIDGET radioWidgetClass # else /* XAW */ # define CHECKBOX_WIDGET toggleWidgetClass # endif /* XAW */ # define CHECKBUTTON_IS_SET XtNstate # define PUSHBUTTON_WIDGET commandWidgetClass # define VALUE_CALLBACK_NAME XtNcallback # define ACTIVATE_CALLBACK_NAME XtNcallback #endif /* MOTIF */ #if defined(MOTIF) && USE_COMBOBOX struct search_history { char **char_items; size_t item_cnt; Boolean empty; }; #define Xdvi_HISTORY_EMPTY_MARKER "" static void search_history_init(struct search_history *hist) { if (resource.search_history == NULL) { hist->empty = True; hist->char_items = xmalloc(1 * sizeof *(hist->char_items)); hist->char_items[0] = NULL; hist->item_cnt = 0; } else { hist->char_items = get_separated_list(resource.search_history, "\n", False); hist->empty = False; /* count number of items and save to hist->item_cnt */ for (hist->item_cnt = 0; hist->char_items[hist->item_cnt] != NULL; hist->item_cnt++) { ; } } } static void search_history_update(struct search_history *hist, Widget w, const char *str) { size_t i; char *tmp; XmString tmp_str = XmStringCreateLocalized((char *)str); Boolean found = False; Widget textfield = NULL; TRACE_FIND((stderr, "search_history_update for: |%s|\n", str)); /* special case if Xdvi_HISTORY_EMPTY_MARKER is present: Replace it with `str' */ if (hist->empty) { XmComboBoxDeletePos(w, 0); hist->empty = False; hist->char_items = string_list_prepend(hist->char_items, str); XmComboBoxAddItem(w, tmp_str, 0, True); hist->item_cnt++; XmStringFree(tmp_str); return; } XtVaGetValues(w, XmNtextField, &textfield, NULL); if (textfield == NULL) { XDVI_ERROR((stderr, "Could not get text field from combo box!")); XmStringFree(tmp_str); return; } /* if str is already in the history, just move it to the first position */ for (i = 0; hist->char_items[i] != NULL; i++) { if (strcmp(hist->char_items[i], str) == 0) { found = True; break; } } if (found) { hist->char_items = string_list_move_to_start(hist->char_items, i); XmComboBoxDeletePos(w, i + 1); XmComboBoxAddItem(w, tmp_str, 1, True); XmComboBoxSelectItem(w, tmp_str); XtVaSetValues(textfield, XmNcursorPosition, strlen(hist->char_items[0]), NULL); XmStringFree(tmp_str); return; } if ((int)hist->item_cnt >= resource.search_history_size) { /* history has reached its max size, delete the oldest element and put the new element at the beginning */ XmComboBoxDeletePos(w, hist->item_cnt); free(hist->char_items[hist->item_cnt - 1]); hist->char_items[hist->item_cnt - 1] = xstrdup(str); hist->char_items = string_list_rotate_up(hist->char_items); XmComboBoxAddItem(w, tmp_str, 1, False); XmComboBoxSelectItem(w, tmp_str); XtVaSetValues(textfield, XmNcursorPosition, strlen(str), NULL); } else { /* add item */ hist->char_items = string_list_prepend(hist->char_items, str); XmComboBoxAddItem(w, tmp_str, 1, False); XmComboBoxSelectItem(w, tmp_str); XtVaSetValues(textfield, XmNcursorPosition, strlen(str), NULL); hist->item_cnt++; } tmp = string_list_to_str(hist->char_items, "\n"); TRACE_FIND((stderr, "Saving search history: |%s|\n", tmp)); store_preference(NULL, "searchHistory", "%s", tmp); free(tmp); XmStringFree(tmp_str); } /* * User selected an item from seach history list */ static void cb_search_history_select(Widget w, XtPointer client_data, XtPointer call_data) { XmComboBoxCallbackStruct *cb = (XmComboBoxCallbackStruct *)call_data; struct search_history *hist = NULL; Widget textfield; int pos; int idx; UNUSED(client_data); if (cb->event == NULL) /* only browsing, no selection */ return; pos = cb->item_position; TRACE_FIND((stderr, "POS: %d; reason: %d, event type: %d\n", pos, cb->reason, cb->event->type)); /* Return if user didn't select from menu, but entered text directly. Strangely, `pos == 0' doesn't indicate this, contrary to what the docs say about XmONE_BASED. */ if (pos == 0 || cb->event->type != ButtonRelease) return; XtVaGetValues(w, XmNuserData, &hist, XmNtextField, &textfield, NULL); if (hist == NULL || textfield == NULL) { XDVI_ERROR((stderr, "Couldn't get XmNuserData or XmNtextField from combo box widget!\n")); return; } idx = pos - 1; if (idx == 0 && hist->char_items[0] == NULL) { /* Xdvi_HISTORY_EMPTY_MARKER selected, do nothing */ XtVaSetValues(textfield, XmNvalue, "", NULL); } else { XmString tmp_str; TRACE_FIND((stderr, "moving item %d, %s to pos 0\n", idx, hist->char_items[idx])); hist->char_items = string_list_move_to_start(hist->char_items, idx); /* same for list in combo box */ tmp_str = XmStringCreateLocalized(hist->char_items[0]); XmComboBoxDeletePos(w, pos); XmComboBoxAddItem(w, tmp_str, 1, True); XmComboBoxSelectItem(w, tmp_str); XtVaSetValues(textfield, XmNcursorPosition, strlen(hist->char_items[0]), NULL); XmStringFree(tmp_str); } } #endif /* defined(MOTIF) && USE_COMBOBOX */ static Boolean m_find_popup_active = False; /* flags for setting single bits in resource.search_window_defaults */ static const int SETTINGS_USE_REGEXP_FLAG = 1; static const int SETTINGS_CASE_SENSITIVE_FLAG = 2; static const int SETTINGS_BACKWARDS_FLAG = 4; static const int SETTINGS_IGNORE_LINEBREAKS_FLAG = 8; static const int SETTINGS_WRAP_FLAG = 16; static void cb_search_go(Widget w, XtPointer client_data, XtPointer call_data) { struct search_settings *settings = (struct search_settings *)client_data; Widget find_popup; Position x = 0, y = 0; ASSERT(settings != NULL, "client_data cb_search_go mustn't be NULL!"); UNUSED(call_data); TRACE_FIND((stderr, "cb_search_go: settings: searchterm=|%s|, down = %d, re = %d, case = %d, wrap = %d", settings->term, settings->direction, settings->use_regexp, settings->case_sensitive, settings->wrap)); if (!get_widget_by_name(&find_popup, globals.widgets.top_level, "find_popup", True)) return; XtVaGetValues(find_popup, XtNx, &x, XtNy, &y, NULL); /* add some offsets */ settings->x_pos = x + 10; settings->y_pos = y + 10; TRACE_GUI((stderr, "SHELL: %ld; x: %d, y: %d", (unsigned long)w, x, y)); settings->from_page = current_page; settings->message_window = 0; settings->wrapcnt = 0; TRACE_FIND((stderr, "starting search from page: %d", settings->from_page)); search_dvi((XtPointer)settings); } static void show_settings(const char *func_name, struct search_settings *settings) { TRACE_FIND((stderr, "%s: settings: str=|%s|, down = %d, re = %d, " "case = %d, ignore_hyphens = %d, ignore_linebreaks = %d, wrap = %d\n", func_name, settings->term, settings->direction, settings->use_regexp, settings->case_sensitive, settings->ignore_hyphens, settings->ignore_linebreaks, settings->wrap)); } static void cb_regexp_search(Widget w, XtPointer client_data, XtPointer call_data) { struct search_settings *settings = (struct search_settings *)client_data; /* Hack: Since XawToggleUnsetCurrent() will invoke this callback again, return every second time to avoid multiple popups. This is done by setting the flag `do_return'. */ #if !HAVE_REGEX_H static Boolean do_return = False; #endif ASSERT(settings != NULL, "client_data cb_search_go mustn't be NULL!"); UNUSED(call_data); #if !HAVE_REGEX_H if (do_return) { do_return = False; return; } XBell(DISP, 0); popup_message(get_matching_parent(w, globals.widgets.top_level, "find_popup", NULL), MSG_ERR, NULL, "Sorry, regular expression support (regex.h) is not available on this platform."); do_return = True; #ifdef MOTIF XmToggleButtonGadgetSetState(w, True, False); #else XawToggleUnsetCurrent(w); #endif #else UNUSED(w); settings->use_regexp = !(settings->use_regexp); /* force re-initialization of search term by resetting utf8_term: */ free(settings->utf8_term); settings->utf8_term = NULL; show_settings("cb_regexp_search", settings); #endif if (settings->use_regexp) resource.search_window_defaults |= SETTINGS_USE_REGEXP_FLAG; else resource.search_window_defaults &= ~SETTINGS_USE_REGEXP_FLAG; store_preference(NULL, "searchWindowDefaults", "%u", resource.search_window_defaults); } static void cb_backwards_search(Widget w, XtPointer client_data, XtPointer call_data) { struct search_settings *settings = (struct search_settings *)client_data; ASSERT(settings != NULL, "client_data cb_search_go mustn't be NULL!"); UNUSED(w); UNUSED(call_data); switch (settings->direction) { case SEARCH_UP: settings->direction = SEARCH_DOWN; break; case SEARCH_DOWN: settings->direction = SEARCH_UP; break; default: ASSERT(0, "shouldn't happen: settings->direction is neither SEARCH_UP nor SEARCH_DOWN!"); break; } show_settings("cb_backwards_search", settings); if (settings->direction == SEARCH_UP) resource.search_window_defaults |= SETTINGS_BACKWARDS_FLAG; else resource.search_window_defaults &= ~SETTINGS_BACKWARDS_FLAG; store_preference(NULL, "searchWindowDefaults", "%u", resource.search_window_defaults); } static void cb_wrap_search(Widget w, XtPointer client_data, XtPointer call_data) { struct search_settings *settings = (struct search_settings *)client_data; ASSERT(settings != NULL, "client_data cb_search_go mustn't be NULL!"); UNUSED(w); UNUSED(call_data); settings->wrap = !settings->wrap; show_settings("cb_wrap_search", settings); if (settings->wrap) resource.search_window_defaults |= SETTINGS_WRAP_FLAG; else resource.search_window_defaults &= ~SETTINGS_WRAP_FLAG; store_preference(NULL, "searchWindowDefaults", "%u", resource.search_window_defaults); } static void cb_match_case(Widget w, XtPointer client_data, XtPointer call_data) { struct search_settings *settings = (struct search_settings *)client_data; ASSERT(settings != NULL, "client_data cb_search_go mustn't be NULL!"); UNUSED(w); UNUSED(call_data); settings->case_sensitive = !(settings->case_sensitive); show_settings("cb_match_case", settings); if (settings->case_sensitive) resource.search_window_defaults |= SETTINGS_CASE_SENSITIVE_FLAG; else resource.search_window_defaults &= ~SETTINGS_CASE_SENSITIVE_FLAG; store_preference(NULL, "searchWindowDefaults", "%u", resource.search_window_defaults); } static void cb_linebreaks(Widget w, XtPointer client_data, XtPointer call_data) { struct search_settings *settings = (struct search_settings *)client_data; ASSERT(settings != NULL, "client_data cb_search_go mustn't be NULL!"); UNUSED(w); UNUSED(call_data); settings->ignore_linebreaks = !(settings->ignore_linebreaks); settings->ignore_hyphens = !(settings->ignore_hyphens); show_settings("cb_linebreaks", settings); if (settings->ignore_linebreaks) resource.search_window_defaults |= SETTINGS_IGNORE_LINEBREAKS_FLAG; else resource.search_window_defaults &= ~SETTINGS_IGNORE_LINEBREAKS_FLAG; store_preference(NULL, "searchWindowDefaults", "%u", resource.search_window_defaults); } static void cb_search_cancel(Widget w, XtPointer client_data, XtPointer call_data) { Widget find_popup; struct search_settings *settings = (struct search_settings *)client_data; UNUSED(w); UNUSED(call_data); if (!get_widget_by_name(&find_popup, globals.widgets.top_level, "find_popup", True)) return; /* This flag is checked in the scanning routines, and will be eventually reset by do_pages() in events.c. (The scanning routines musn't reset it, since they might not be called again after this point!) */ globals.ev.flags |= EV_FIND_CANCEL; search_signal_page_changed(); /* Hack to make search restart anew */ search_erase_highlighting(True); if (settings->message_window != 0) { TRACE_GUI((stderr, "kill_message_window: %p\n", (void *)settings->message_window)); kill_message_window(settings->message_window); } XtPopdown(find_popup); m_find_popup_active = False; } static void cb_search_get_term_button(Widget w, XtPointer client_data, XtPointer call_data) { struct search_settings *settings = (struct search_settings *)client_data; char *searchterm = NULL; #if defined(MOTIF) && USE_COMBOBOX struct search_history *hist = NULL; #endif Widget searchbox_input, paned, box; UNUSED(call_data); box = XtParent(w); ASSERT(box != 0, "Parent of button widget mustn't be NULL!"); paned = XtParent(box); ASSERT(paned != 0, "Parent of box widget mustn't be NULL!"); if (!get_widget_by_name(&searchbox_input, paned, Xdvi_SEARCHBOX_INPUT_NAME, True)) return; #ifdef MOTIF # if USE_COMBOBOX { Widget textfield = NULL; XtVaGetValues(searchbox_input, XmNtextField, &textfield, XmNuserData, &hist, NULL); if (textfield == NULL || hist == NULL) { XDVI_ERROR((stderr, "Couldn't get XmNtextField or XmNuserData from combo box widget!\n")); return; } XtVaGetValues(textfield, XmNvalue, &searchterm, NULL); } # else XtVaGetValues(searchbox_input, XmNvalue, &searchterm, NULL); # endif #else XtVaGetValues(searchbox_input, XtNstring, &searchterm, NULL); #endif if (searchterm == NULL) { XDVI_WARNING((stderr, "Searchterm in cb_search_get_term callback shouldn't be NULL!")); return; } TRACE_FIND((stderr, "searchterm1: |%s|", searchterm)); settings->term = searchterm; #if defined(MOTIF) && USE_COMBOBOX search_history_update(hist, searchbox_input, searchterm); #endif cb_search_go(w, settings, NULL); } static void search_cancel(Widget w, XEvent *event, String *params, Cardinal *num_params) { struct search_settings *settings = NULL; void *ptr; UNUSED(event); ASSERT(*num_params > 0, "params in search_cancel must be > 0!"); ASSERT(*params != NULL, "params in search_cancel mustn't be NULL!"); /* * the *params char pointer contains the pointer value (address) of `settings'; * convert it back to a pointer: */ TRACE_GUI((stderr, "Pointer string value: |%s|", *params)); sscanf(*params, "%p", &ptr); settings = (struct search_settings *)ptr; TRACE_GUI((stderr, "Pointer address: |%p|", (void *)settings)); ASSERT(settings != NULL, "Shouldn't happen: Couldn't get string representation of argument pointer."); cb_search_cancel(w, settings, NULL); } #ifdef MOTIF static void cb_search_get_term(Widget w, XtPointer client_data, XtPointer call_data) { struct search_settings *settings = (struct search_settings *)client_data; char *searchterm = NULL; Widget popup, button, textfield; XEvent ev; UNUSED(call_data); TRACE_FIND((stderr, "cb_search_get_term!\n")); #if USE_COMBOBOX UNUSED(w); { Widget combobox; if (!get_widget_by_name(&combobox, globals.widgets.top_level, Xdvi_SEARCHBOX_INPUT_NAME, True) || !get_widget_by_name(&textfield, globals.widgets.top_level, "Text", True)) return; } #else textfield = w; #endif /* retrieve the `Find' button widget */ if (!get_widget_by_name(&popup, globals.widgets.top_level, Xdvi_SEARCH_POPUP_NAME, True) || !get_widget_by_name(&button, popup, "find_button", True)) return; if (settings != NULL) { /* retrieve search term from text input field */ XtVaGetValues(textfield, XmNvalue, &searchterm, NULL); if (searchterm == NULL) { XDVI_WARNING((stderr, "Searchterm in cb_search_get_term callback shouldn't be NULL!")); return; } TRACE_FIND((stderr, "searchterm2: |%s|", searchterm)); settings->term = (const char *)searchterm; } /* make the `Find' button think it got pushed. Also synthesize an event, just to be sure ... */ synthesize_event(&ev, button); XtCallActionProc(button, "ArmAndActivate", &ev, NULL, 0); /* the following don't make the button appear visually pressed: */ /* XtCallCallbacks(button, XmNarmCallback, NULL); */ /* XtCallCallbacks(button, XmNactivateCallback, NULL); */ } static void xm_search_go(Widget w, XEvent *event, String *params, Cardinal *num_params) { struct search_settings *settings = NULL; void *ptr; Widget input; UNUSED(w); UNUSED(event); if (params != NULL) { /* re-initialize search term */ ASSERT(*num_params == 1, "num_params in xm_search_go should be 1 if *params != NULL"); /* * the *params char pointer contains the pointer value (address) of `settings' as a string; * convert it back to a pointer: */ TRACE_GUI((stderr, "Pointer string value: |%s|", *params)); sscanf(*params, "%p", &ptr); settings = (struct search_settings *)ptr; TRACE_GUI((stderr, "Pointer address: |%p|", (void *)settings)); ASSERT(settings != NULL, "Shouldn't happen: Couldn't get string representation of argument pointer."); } if (get_widget_by_name(&input, globals.widgets.top_level, "searchbox_input", True)) { cb_search_get_term(input, settings, NULL); } } #else /* MOTIF */ static void xaw_unset_button(XtPointer client_data, XtIntervalId *id) { XEvent ev; Widget button = (Widget)client_data; UNUSED(id); synthesize_event(&ev, button); XtCallActionProc(button, "unset", &ev, NULL, 0); } static void xaw_search_go(Widget w, XEvent *event, String *params, Cardinal *num_params) { struct search_settings *settings = NULL; void *ptr; char *searchterm = NULL; Widget button, input; XtIntervalId timeout; UNUSED(w); if (!get_widget_by_name(&button, globals.widgets.top_level, "find_button", True) || !get_widget_by_name(&input, globals.widgets.top_level, "searchbox_input", True)) return; XtVaGetValues(input, XtNstring, &searchterm, NULL); if (searchterm == NULL) { XDVI_WARNING((stderr, "Searchterm in xaw_search_go callback shouldn't be NULL!")); return; } if (params != NULL) { /* re-initialize search term */ ASSERT(*num_params == 1, "num_params in xaw_search_go should be 1 if *params != NULL"); /* * the *params char pointer contains the pointer value (address) of `settings'; * convert it back to a pointer: */ TRACE_GUI((stderr, "Pointer string value: |%s|", *params)); sscanf(*params, "%p", &ptr); settings = (struct search_settings *)ptr; TRACE_GUI((stderr, "Pointer address: |%p|", (void *)settings)); ASSERT(settings != NULL, "Shouldn't happen: Couldn't get string representation of argument pointer."); settings->term = (const char *)searchterm; } /* * Again, we want the button to appear activated when is pressed. * For this, we explicitly invoke set()notify(), and unset() after a timeout * of 150 milliseconds, which seems to correspond to the Motif default. */ XtCallActionProc(button, "set", event, NULL, 0); XtCallActionProc(button, "notify", event, NULL, 0); XSync(DISP, False); timeout = XtAppAddTimeOut(globals.app, 150, xaw_unset_button, (XtPointer)button); } /* * Restart search from within another popup (confirmation) window. */ static void xaw_search_restart(Widget w, XEvent *event, String *params, Cardinal *num_params) { void *ptr; struct search_settings *settings; UNUSED(event); UNUSED(w); ASSERT(*num_params > 0, "params in xaw_search_restart must be > 0!"); ASSERT(*params != NULL, "params in xaw_search_restart mustn't be NULL!"); TRACE_GUI((stderr, "Pointer string value: |%s|", *params)); sscanf(*params, "%p", &ptr); settings = (struct search_settings *)ptr; search_restart((XtPointer)settings); } static XtActionsRec xaw_search_actions[] = { { "do-search-restart", xaw_search_restart }, { "do-search", xaw_search_go }, }; #endif /* MOTIF */ static XtActionsRec search_actions[] = { { "do-search", #ifdef MOTIF xm_search_go #else xaw_search_go #endif }, { "cancel-search", search_cancel }, }; static Widget create_search_window(struct search_settings *settings) { Widget top_level_shell; Atom WM_DELETE_WINDOW; Widget form, find_paned, box; unsigned char curr_state; #if defined(MOTIF) && USE_COMBOBOX static struct search_history hist = { NULL, 0, True }; #endif Widget searchbox_form, searchbox_label, searchbox_input; /* left and right column */ Widget options_left_form, options_right_form; /* checkboxes in left column */ Widget regexp_checkbox, matchcase_checkbox, backwards_checkbox, wrap_checkbox; /* checkboxes in right column */ Widget linebreaks_checkbox; Widget find_button, cancel_button; XtTranslations xlats; char *translation_str = NULL; #ifdef MOTIF XmString str; #else /* MOTIF */ XtTranslations wm_translations; int ddist; #endif XtAddActions(search_actions, XtNumber(search_actions)); #ifndef MOTIF XtAddActions(xaw_search_actions, XtNumber(xaw_search_actions)); #define HORIZONTAL_RESIZING_NO XtNleft, XtChainLeft, XtNright, XtChainLeft #define HORIZONTAL_RESIZING_YES XtNleft, XtChainLeft, XtNright, XtChainRight #define VERTICAL_RESIZING_NO XtNtop, XtChainTop, XtNbottom, XtChainTop /* * hacky: Since translations can only contain strings, pass * the pointer address as string in the callback. But the only * portability problem should be the size of the pointer * representation, and we deal with that by using VSNPRINTF(). */ translation_str = get_string_va("WM_PROTOCOLS: cancel-search(%p)\nCtrlg:find-next()", (void *)settings); wm_translations = XtParseTranslationTable(translation_str); free(translation_str); translation_str = NULL; #endif /* not MOTIF */ translation_str = get_string_va( #ifdef MOTIF "osfCancel:cancel-search(%p)\n" #else "Escape:cancel-search(%p)\n" #endif "Return:do-search(%p)\n" "Ctrlg:find-next()", (void *)settings, (void *)settings); xlats = XtParseTranslationTable(translation_str); free(translation_str); top_level_shell = XtVaCreatePopupShell("find_popup", SHELL_WIDGET, globals.widgets.top_level, XtNtitle, "xdvik: Find in File", XtNmappedWhenManaged, False, /* so that we can center it first */ XtNtransientFor, globals.widgets.top_level, XtNallowShellResize, True, #ifdef MOTIF XmNdeleteResponse, XmDO_NOTHING, /* we'll take care of that ourselves */ #else XtNtranslations, wm_translations, #endif NULL); TRACE_GUI((stderr, "toplevel: %ld", (unsigned long)top_level_shell)); WM_DELETE_WINDOW = XInternAtom(XtDisplay(top_level_shell), "WM_DELETE_WINDOW", False); #ifdef MOTIF XmAddWMProtocolCallback(top_level_shell, WM_DELETE_WINDOW, cb_search_cancel, settings); #endif find_paned = XtVaCreateWidget("find_paned", PANED_WIDGET, top_level_shell, #ifdef MOTIF /* make sashes invisible */ XmNsashWidth, 1, XmNsashHeight, 1, #endif NULL); form = XtVaCreateWidget("form", FORM_WIDGET, find_paned, #ifdef MOTIF XmNhorizontalSpacing, DDIST_MAJOR, XmNverticalSpacing, DDIST_MAJOR, XmNautoUnmanage, False, #else XtNallowResize, True, #endif NULL); #ifdef MOTIF str = XmStringCreateLocalized("Find:"); #else XtVaGetValues(form, XtNdefaultDistance, &ddist, NULL); #endif /* search term input field */ searchbox_form = XtVaCreateWidget("searchbox_form", FORM_WIDGET, form, #ifdef MOTIF XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, XmNhorizontalSpacing, DDIST, XmNverticalSpacing, DDIST, #else XtNborderWidth, 0, HORIZONTAL_RESIZING_YES, #endif NULL); searchbox_label = XtVaCreateManagedWidget("searchbox_label", LABEL_WIDGET, searchbox_form, #ifdef MOTIF XmNlabelString, str, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNalignment, XmALIGNMENT_BEGINNING, #else XtNlabel, "Find:", XtNborderWidth, 0, HORIZONTAL_RESIZING_NO, VERTICAL_RESIZING_NO, #endif NULL); #if defined(MOTIF) && USE_COMBOBOX { Widget textfield = NULL; Widget grab_shell = NULL; size_t n; XmStringTable items; search_history_init(&hist); /* initialize Motif string list */ n = hist.item_cnt == 0 ? 1 : hist.item_cnt; items = (XmStringTable)XtMalloc(n * sizeof(XmString *)); if (hist.item_cnt == 0) items[0] = XmStringCreateLocalized(Xdvi_HISTORY_EMPTY_MARKER); else { for (n = 0; hist.char_items[n] != NULL; n++) items[n] = XmStringCreateLocalized(hist.char_items[n]); } searchbox_input = XtVaCreateManagedWidget(Xdvi_SEARCHBOX_INPUT_NAME, xmComboBoxWidgetClass, searchbox_form, XmNtopAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, searchbox_label, XmNbottomAttachment, XmATTACH_FORM, XmNcomboBoxType, XmDROP_DOWN_COMBO_BOX, XmNitems, items, XmNitemCount, n, /* XmONE_BASED so that we can distinguish between keyboard entry and selection from popdown list */ XmNpositionMode, XmONE_BASED, /* XmNitemCount, hist.item_cnt, */ /* XmNvisibleItemCount, hist.item_cnt, */ XmNuserData, &hist, XmNarrowSize, Xdvi_COMBO_BOX_ARROW_SIZE, NULL); if (resource.search_history_size < 10) { /* usually the dropdown list shows 10 entries */ XtVaSetValues(searchbox_input, XmNvisibleItemCount, resource.search_history_size, NULL); } /* free Motif strings */ if (hist.item_cnt == 0) XmStringFree(items[0]); else { for (n = 0; hist.char_items[n] != NULL; n++) XmStringFree(items[n]); } XtFree((XtPointer)items); /* start with empty search field */ XtVaGetValues(searchbox_input, XmNtextField, &textfield, NULL); if (textfield == NULL) XDVI_ERROR((stderr, "Couldn't get textfield from combo box widget!\n")); if (settings->term != NULL) { XtVaSetValues(textfield, XmNvalue, settings->term, NULL); } else { XtVaSetValues(textfield, XmNvalue, "", NULL); } XtAddCallback(textfield, XmNactivateCallback, cb_search_get_term, settings); XtAddCallback(searchbox_input, XmNselectionCallback, cb_search_history_select, (XtPointer)settings); XtOverrideTranslations(textfield, xlats); /* workaround for pointer grabbing bug (see xm_menu.c) */ if (get_widget_by_name(&grab_shell, searchbox_input, "GrabShell", True)) { XtAddCallback(grab_shell, XtNpopdownCallback, popdown_callback, NULL); } } #else /* defined(MOTIF) && USE_COMBOBOX */ searchbox_input = XtVaCreateManagedWidget(Xdvi_SEARCHBOX_INPUT_NAME, TEXT_WIDGET, searchbox_form, #ifdef MOTIF XmNtopAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, searchbox_label, XmNbottomAttachment, XmATTACH_FORM, #else XtNwidth, 260, XtNdataCompression, False, XtNeditType, XawtextEdit, XtNfromHoriz, searchbox_label, HORIZONTAL_RESIZING_YES, VERTICAL_RESIZING_NO, #endif NULL); #ifdef MOTIF XtAddCallback(searchbox_input, XmNactivateCallback, cb_search_get_term, settings); #endif if (settings->term != NULL) { XtVaSetValues(searchbox_input, #ifdef MOTIF XmNvalue, #else XtNstring, #endif settings->term, NULL); } #endif /* defined(MOTIF) && USE_COMBOBOX */ XtManageChild(searchbox_form); /* Fix for #1499566: Force input focus for text input field */ #ifndef MOTIF XtSetKeyboardFocus(find_paned, searchbox_input); #endif /* * form for left row of options checkbuttons */ options_left_form = XtVaCreateManagedWidget("options_left_form", FORM_WIDGET, form, #ifdef MOTIF XmNhorizontalSpacing, DDIST, XmNverticalSpacing, DDIST, XmNresizable, True, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, searchbox_form, XmNleftAttachment, XmATTACH_FORM, /* XmNrightAttachment, XmATTACH_FORM, */ /* XmNbottomAttachment, XmATTACH_FORM, */ #else XtNborderWidth, 0, XtNfromVert, searchbox_form, HORIZONTAL_RESIZING_NO, #endif NULL); /* * form for right row of options checkbuttons */ options_right_form = XtVaCreateManagedWidget("options_right_form", FORM_WIDGET, form, #ifdef MOTIF XmNhorizontalSpacing, DDIST, XmNverticalSpacing, DDIST, XmNresizable, True, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, searchbox_form, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, options_left_form, XmNrightAttachment, XmATTACH_FORM, /* XmNbottomAttachment, XmATTACH_FORM, */ #else XtNborderWidth, 0, XtNfromVert, searchbox_form, XtNfromHoriz, options_left_form, HORIZONTAL_RESIZING_NO, #endif NULL); #ifdef MOTIF XmStringFree(str); str = XmStringCreateLocalized("Regular expression"); #endif regexp_checkbox = XtVaCreateManagedWidget("regexp_checkbox", CHECKBOX_WIDGET, options_left_form, #ifdef MOTIF XmNlabelString, str, XmNindicatorType, XmN_OF_MANY, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, #else XtNlabel, "Regular expression", XtNborderWidth, 0, XtNisRadio, False, XtNhighlightThickness, 1, HORIZONTAL_RESIZING_NO, VERTICAL_RESIZING_NO, #endif NULL); XtAddCallback(regexp_checkbox, VALUE_CALLBACK_NAME, cb_regexp_search, settings); #ifdef MOTIF XmStringFree(str); str = XmStringCreateLocalized("Find backwards"); #endif backwards_checkbox = XtVaCreateManagedWidget("backwards_checkbox", CHECKBOX_WIDGET, options_left_form, #ifdef MOTIF XmNlabelString, str, XmNindicatorType, XmN_OF_MANY, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, regexp_checkbox, XmNleftAttachment, XmATTACH_FORM, #else XtNlabel, "Find backwards", XtNfromVert, regexp_checkbox, XtNborderWidth, 0, XtNisRadio, False, XtNhighlightThickness, 1, HORIZONTAL_RESIZING_NO, VERTICAL_RESIZING_NO, #endif NULL); XtAddCallback(backwards_checkbox, VALUE_CALLBACK_NAME, cb_backwards_search, settings); #ifdef MOTIF XmStringFree(str); str = XmStringCreateLocalized("Wrap search"); #endif wrap_checkbox = XtVaCreateManagedWidget("backwards_checkbox", CHECKBOX_WIDGET, options_left_form, #ifdef MOTIF XmNlabelString, str, XmNindicatorType, XmN_OF_MANY, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, backwards_checkbox, XmNleftAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, #else XtNlabel, "Wrap search", XtNfromVert, backwards_checkbox, XtNborderWidth, 0, XtNisRadio, False, XtNhighlightThickness, 1, HORIZONTAL_RESIZING_NO, VERTICAL_RESIZING_NO, #endif NULL); XtAddCallback(wrap_checkbox, VALUE_CALLBACK_NAME, cb_wrap_search, settings); #ifdef MOTIF XmStringFree(str); str = XmStringCreateLocalized("Case sensitive"); #endif matchcase_checkbox = XtVaCreateManagedWidget("matchcase_checkbox", CHECKBOX_WIDGET, options_right_form, #ifdef MOTIF XmNlabelString, str, XmNindicatorType, XmN_OF_MANY, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, #else XtNlabel, "Case sensitive", XtNborderWidth, 0, XtNisRadio, False, XtNhighlightThickness, 1, HORIZONTAL_RESIZING_NO, VERTICAL_RESIZING_NO, #endif NULL); XtAddCallback(matchcase_checkbox, VALUE_CALLBACK_NAME, cb_match_case, settings); #ifdef MOTIF XmStringFree(str); str = XmStringCreateLocalized("Ignore newlines/hyphens"); #endif linebreaks_checkbox = XtVaCreateManagedWidget("linebreaks_checkbox", CHECKBOX_WIDGET, options_right_form, #ifdef MOTIF XmNlabelString, str, XmNindicatorType, XmN_OF_MANY, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, matchcase_checkbox, XmNleftAttachment, XmATTACH_FORM, #else XtNlabel, "Ignore newlines/hyphens", XtNfromVert, matchcase_checkbox, XtNborderWidth, 0, XtNisRadio, False, XtNhighlightThickness, 1, HORIZONTAL_RESIZING_NO, VERTICAL_RESIZING_NO, #endif NULL); XtAddCallback(linebreaks_checkbox, VALUE_CALLBACK_NAME, cb_linebreaks, settings); /* * box for Find/Cancel buttons */ box = XtVaCreateManagedWidget("box", FORM_WIDGET, find_paned, #ifdef MOTIF XmNskipAdjust, True, /* don't resize this area */ #else /* resizing by user isn't needed */ XtNshowGrip, False, XtNdefaultDistance, 6, /* some padding */ /* resizing the window shouldn't influence this box, * but only the pane widget */ XtNskipAdjust, True, XtNaccelerators, G_accels_cr, #endif NULL); #ifdef MOTIF XmStringFree(str); str = XmStringCreateLocalized("Find"); #endif find_button = XtVaCreateManagedWidget("find_button", PUSHBUTTON_WIDGET, box, #ifdef MOTIF XmNlabelString, str, XmNshowAsDefault, True, XmNdefaultButtonShadowThickness, 1, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, /* to mimick appearance of native dialog buttons: */ XmNmarginWidth, 6, XmNmarginHeight, 4, XmNtopOffset, 10, XmNbottomOffset, 10, XmNleftOffset, 10, #else XtNlabel, "Find", XtNaccelerators, G_accels_cr, XtNtop, XtChainTop, XtNbottom, XtChainBottom, HORIZONTAL_RESIZING_NO, #endif NULL); #ifdef MOTIF XmStringFree(str); str = XmStringCreateLocalized("Cancel"); #endif cancel_button = XtVaCreateManagedWidget("cancel_button", PUSHBUTTON_WIDGET, box, #ifdef MOTIF XmNlabelString, str, XmNdefaultButtonShadowThickness, 1, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, /* to mimick appearance of native dialog buttons: */ XmNmarginWidth, 6, XmNmarginHeight, 4, XmNtopOffset, 10, XmNbottomOffset, 10, XmNrightOffset, 10, #else XtNlabel, "Cancel", XtNfromHoriz, find_button, XtNbottom, XtChainBottom, XtNjustify, XtJustifyRight, XtNleft, XtChainRight, XtNright, XtChainRight, #endif NULL); #ifdef MOTIF XmStringFree(str); #endif XtAddCallback(cancel_button, ACTIVATE_CALLBACK_NAME, cb_search_cancel, settings); XtAddCallback(find_button, ACTIVATE_CALLBACK_NAME, cb_search_get_term_button, settings); XtOverrideTranslations(searchbox_form, xlats); XtOverrideTranslations(options_left_form, xlats); XtOverrideTranslations(options_right_form, xlats); XtOverrideTranslations(regexp_checkbox, xlats); XtOverrideTranslations(matchcase_checkbox, xlats); XtOverrideTranslations(backwards_checkbox, xlats); XtOverrideTranslations(box, xlats); XtOverrideTranslations(find_button, xlats); XtOverrideTranslations(cancel_button, xlats); XtOverrideTranslations(searchbox_input, xlats); /* following doesn't help to force input to textbox in xaw: */ /* XtInstallAllAccelerators(find_paned, find_paned); */ /* XtInstallAllAccelerators(searchbox_input, find_paned); */ XtManageChild(form); XtManageChild(find_paned); { /* set all buttons to same size */ Dimension w1, w2, max; XtVaGetValues(find_button, XtNwidth, &w1, NULL); XtVaGetValues(cancel_button, XtNwidth, &w2, NULL); max = MAX(w1, w2); XtVaSetValues(find_button, XtNwidth, max, NULL); XtVaSetValues(cancel_button, XtNwidth, max, NULL); } XtManageChild(top_level_shell); /* don't center this one - would just get in the way in this case. */ /* center_window(top_level_shell, globals.widgets.top_level); */ XtPopup(top_level_shell, XtGrabNone); m_find_popup_active = True; #ifdef MOTIF XmProcessTraversal(searchbox_input, XmTRAVERSE_CURRENT); /* XmProcessTraversal(find_button, XmTRAVERSE_CURRENT); */ #endif XSetWMProtocols(XtDisplay(top_level_shell), XtWindow(top_level_shell), &WM_DELETE_WINDOW, 1); /* check if we have default values from resource.search_window_defaults */ if (resource.search_window_defaults & SETTINGS_USE_REGEXP_FLAG) XtVaSetValues(regexp_checkbox, CHECKBUTTON_IS_SET, True, NULL); if (resource.search_window_defaults & SETTINGS_CASE_SENSITIVE_FLAG) XtVaSetValues(matchcase_checkbox, CHECKBUTTON_IS_SET, True, NULL); if (resource.search_window_defaults & SETTINGS_BACKWARDS_FLAG) XtVaSetValues(backwards_checkbox, CHECKBUTTON_IS_SET, True, NULL); if (resource.search_window_defaults & SETTINGS_IGNORE_LINEBREAKS_FLAG) XtVaSetValues(linebreaks_checkbox, CHECKBUTTON_IS_SET, True, NULL); if (resource.search_window_defaults & SETTINGS_WRAP_FLAG) XtVaSetValues(wrap_checkbox, CHECKBUTTON_IS_SET, True, NULL); /* initialize `settings' values according to the checkbox states (in case user has assigned values via X defaults): */ XtVaGetValues(regexp_checkbox, CHECKBUTTON_IS_SET, &curr_state, NULL); settings->use_regexp = curr_state; XtVaGetValues(matchcase_checkbox, CHECKBUTTON_IS_SET, &curr_state, NULL); settings->case_sensitive = curr_state; XtVaGetValues(wrap_checkbox, CHECKBUTTON_IS_SET, &curr_state, NULL); settings->wrap = curr_state; XtVaGetValues(backwards_checkbox, CHECKBUTTON_IS_SET, &curr_state, NULL); settings->direction = curr_state ? SEARCH_UP : SEARCH_DOWN; XtVaGetValues(linebreaks_checkbox, CHECKBUTTON_IS_SET, &curr_state, NULL); settings->ignore_hyphens = settings->ignore_linebreaks = curr_state; #ifndef MOTIF #undef HORIZONTAL_RESIZING_NO #undef HORIZONTAL_RESIZING_YES #undef VERTICAL_RESIZING_NO #endif /* MOTIF */ return top_level_shell; } void dvi_find_string(const char *str, Boolean find_next) { /* Synthesize a RET keystroke for the find dialog. * Also pops up the find dialog, to make it easier for user to * edit options, change direction etc. */ Widget find_popup; if (!get_widget_by_name(&find_popup, globals.widgets.top_level, "find_popup", False)) { static struct search_settings settings; static struct search_info searchinfo = { False, False, False, 0, 0, 0, 0 }; settings.term = str; settings.use_regexp = False; settings.case_sensitive = False; settings.direction = SEARCH_DOWN; settings.ignore_hyphens = False; settings.ignore_linebreaks = False; settings.wrap = False; settings.isearchterm = NULL; settings.wrapcnt = 0; settings.x_pos = -1; settings.y_pos = -1; settings.searchinfo = &searchinfo; settings.hyphen_delta = 0; find_popup = create_search_window(&settings); if (find_next) return; } else if (str != NULL) { /* change the search term */ Widget searchbox_input; if (!get_widget_by_name(&searchbox_input, find_popup, "searchbox_input", True)) return; XtVaSetValues(searchbox_input, #ifdef MOTIF XmNvalue, #else XtNstring, #endif str, NULL); } if (m_find_popup_active) { XRaiseWindow(DISP, XtWindow(find_popup)); } else { XtPopup(find_popup, XtGrabNone); m_find_popup_active = True; } if (str != NULL || find_next) { #ifdef MOTIF xm_search_go(NULL, NULL, NULL, NULL); #else xaw_search_go(NULL, NULL, NULL, NULL); #endif } }