From 487ca4806cc046076293cf6cc5fbba0db282bac7 Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Tue, 17 Jan 2006 21:41:51 +0000 Subject: texk 1 git-svn-id: svn://tug.org/texlive/trunk@1485 c570f23f-e606-0410-a88d-b1316a301751 --- Build/source/texk/xdvik/x_util.c | 1221 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1221 insertions(+) create mode 100644 Build/source/texk/xdvik/x_util.c (limited to 'Build/source/texk/xdvik/x_util.c') diff --git a/Build/source/texk/xdvik/x_util.c b/Build/source/texk/xdvik/x_util.c new file mode 100644 index 00000000000..79dd5a6a531 --- /dev/null +++ b/Build/source/texk/xdvik/x_util.c @@ -0,0 +1,1221 @@ +#include "xdvi-config.h" + +#include +#include +#include +#include + + +#if !MOTIF +# include +#endif + +#include "x_util.h" +#include "string-utils.h" +#include "util.h" +#include "statusline.h" +#include "message-window.h" +#include "events.h" /* for set_bar_value() */ + +#define DEBUG_SCROLL_IF_NEEDED 0 + +Boolean do_autoscroll = False; + + +/* Center window wa over window wb */ +void +center_window(Widget wa, Widget wb) +{ + Position x, y; + Dimension w1, h1, w2, h2; + + if (!XtIsRealized(wa) || !XtIsRealized(wb)) + return; + + XtVaGetValues(wa, + XtNwidth, &w1, + XtNheight, &h1, + NULL); + XtVaGetValues(wb, + XtNwidth, &w2, + XtNheight, &h2, + XtNx, &x, + XtNy, &y, + NULL); + XtVaSetValues(wa, XtNx, x + (w2 - w1) / 2, + XtNy, y + (h2 - h1) / 2, + NULL); +} + +/* Position window w at coordinates x, y */ +void +position_window(Widget w, Position x, Position y) +{ + if (!XtIsRealized(w)) + return; + + TRACE_GUI((stderr, "positioning %ld at %d, %d", (unsigned long)w, x, y)); + XtVaSetValues(w, XtNx, x, XtNy, y, NULL); +} + +/* + Used for the hyperref and forward search markers: scroll the page + to make the marker visible. +*/ +void +scroll_page_if_needed(int x_min, int x_max, int y_min, int y_max) +{ + Position drawing_x, drawing_y, drawing_h, clip_x, clip_y, clip_h, clip_w; + int test_scroll, need_v_scroll = 0, need_h_scroll = 0; + + if (!do_autoscroll) + return; + + XtVaGetValues(globals.widgets.clip_widget, + XtNx, &clip_x, XtNy, &clip_y, + XtNheight, &clip_h, XtNwidth, &clip_w, + NULL); + XtVaGetValues(globals.widgets.draw_widget, + XtNx, &drawing_x, XtNy, &drawing_y, + XtNheight, &drawing_h, + NULL); + +#if DEBUG_SCROLL_IF_NEEDED + fprintf(stderr, "y: %d, drawing_y: %d, clip_h: %d\n", y, drawing_y, clip_h); +#endif + /* check if we need to scroll vertically; first, down for y_min */ + test_scroll = y_min + drawing_y - clip_h; + if ((resource.expert_mode & XPRT_SHOW_STATUSLINE) != 0) + test_scroll += global_statusline_h; + + TRACE_SRC((stderr, "test_scroll vertically: %d", test_scroll)); +#if DEBUG_SCROLL_IF_NEEDED + fprintf(stderr, "%d + %d > %d?\n", drawing_y, y_min, clip_h); +#endif + if (test_scroll > 0) { /* need to scroll down? */ + need_v_scroll = test_scroll; + TRACE_SRC((stderr, "need_v_scroll down: %d", need_v_scroll)); + } + else if (abs(drawing_y) + 2 > y_max) { /* need to scroll up? */ + need_v_scroll = -((abs(drawing_y) - y_max) + 1); + TRACE_SRC((stderr, "need_v_scroll up: %d (%d > %d; %d)", need_v_scroll, abs(drawing_y), y_max, clip_y)); + } + + /* check if we need to scroll horizontally; x_min < 0 blocks this + (e.g. for hyperref, where we don't want it) */ + if (x_min >= 0) { + test_scroll = x_min + drawing_x - clip_w + 1; + TRACE_SRC((stderr, "test_scroll horizontally: %d", test_scroll)); + + if (test_scroll > 0) { + /* need to scroll to right (i.e. make stuff on right-hand side visible)? */ + need_h_scroll = test_scroll; + TRACE_SRC((stderr, "need_h_scroll right: %d", need_h_scroll)); + } + else if (abs(drawing_x) > x_max) { + /* need to scroll to left (i.e. make stuff on left-hand side visible)? */ + need_h_scroll = -(abs(drawing_x) - x_max); + TRACE_SRC((stderr, "need_h_scroll left: %d", need_h_scroll)); + } + } + + /* FIXME: should we not scroll if keep_flag is active? */ + if (need_v_scroll != 0 && globals.widgets.y_bar != NULL) { +#ifdef MOTIF + /* need to add new value to current one */ + XtVaGetValues(globals.widgets.y_bar, XmNvalue, &test_scroll, NULL); + (void)set_bar_value(globals.widgets.y_bar, test_scroll + need_v_scroll, (int)(globals.page.h - mane.height)); +#else + XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc, (XtPointer)need_v_scroll); +#endif + } + + if (need_h_scroll != 0 && globals.widgets.x_bar != NULL) { +#ifdef MOTIF + /* need to add new value to current one */ + XtVaGetValues(globals.widgets.x_bar, XmNvalue, &test_scroll, NULL); + (void)set_bar_value(globals.widgets.x_bar, test_scroll + need_h_scroll, (int)(globals.page.w - mane.width)); +#else + XtCallCallbacks(globals.widgets.x_bar, XtNscrollProc, (XtPointer)need_h_scroll); +#endif + } + + do_autoscroll = False; +} + +void adjust_width(Widget a, Widget b) +{ + Dimension w1, w2; + + XtVaGetValues(a, XtNwidth, &w1, NULL); + XtVaGetValues(b, XtNwidth, &w2, NULL); + + if (w1 < w2) + XtVaSetValues(a, XtNwidth, w2, NULL); + else if (w2 > w1) + XtVaSetValues(b, XtNwidth, w1, NULL); +} + +/* change a GC and return it, or create one if it doesn't exist yet */ +GC +set_or_make_gc(GC gc, int function, Pixel fg, Pixel bg) +{ + XGCValues values; + values.function = function; + values.foreground = fg; + values.background = bg; + + /* Since print is in round dots we make drawings as "smooth" as possible. */ + values.cap_style = CapRound; + values.join_style = JoinRound; + + if (gc != NULL) + XChangeGC(DISP, gc, GCFunction | GCForeground | GCBackground + | GCCapStyle | GCJoinStyle, &values); + else + gc = XCreateGC(DISP, XtWindow(globals.widgets.top_level), GCFunction | GCForeground | GCBackground + | GCCapStyle | GCJoinStyle, &values); + + return gc; +} + +/* + * Atom handling. + */ + +static char *atom_names[] = { + "XDVI_WINDOWS", + "DVI_NAME", + "SRC_GOTO", + "RELOAD", + "NEWDOC", + "NEWPAGE", + "RAISE", + "FIND_STRING", + "REREAD_PREFS" +}; +static Atom atoms[XtNumber(atom_names)]; + +Window +get_window_id(char *window_p) +{ + Window w; + +#ifndef WORD64 + w = (*((xuint32 *) window_p)); +#else +# if WORDS_BIGENDIAN + w = ((unsigned long)wp[0] << 24) | + ((unsigned long)wp[1] << 16) | + ((unsigned long)wp[2] << 8) | + (unsigned long)wp[3]; +# else + w = ((unsigned long)wp[3] << 24) | + ((unsigned long)wp[2] << 16) | + ((unsigned long)wp[1] << 8) | + (unsigned long)wp[0]; +# endif +#endif + return w; +} + +size_t +property_get_data(Window w, Atom a, char **ret_buf, + int (*x_get_property)(Display *, Window, Atom, long, + long, Bool, Atom, Atom *, int *, unsigned long *, + unsigned long *, unsigned char **)) +{ + /* all of these are in 8-bit units */ + unsigned long byte_offset = 0; + Atom type_ret; + int format_ret; + unsigned long nitems_ret; + unsigned long bytes_after_ret = 0; + unsigned char *prop_ret = NULL; + + /* + * buffer for collecting returned data; this is static to + * avoid expensive malloc()s at every call (which is often!) + */ + static unsigned char *buffer = NULL; + static size_t buffer_len = 0; + + while (x_get_property(DISP, w, + a, byte_offset / 4, (bytes_after_ret + 3) / 4, False, + a, &type_ret, &format_ret, &nitems_ret, + &bytes_after_ret, &prop_ret) + == Success) { + + if (type_ret != a || format_ret == 0) + break; + + nitems_ret *= (format_ret / 8); /* convert to bytes */ + + while ((byte_offset + nitems_ret) >= buffer_len) { + buffer_len += 256; + buffer = xrealloc(buffer, buffer_len); + } + + /* the +1 captures the extra '\0' that Xlib puts after the end. */ + memcpy(buffer + byte_offset, prop_ret, nitems_ret + 1); + byte_offset += nitems_ret; + + XFree(prop_ret); + prop_ret = NULL; + + if (bytes_after_ret == 0) /* got all data */ + break; + } + + if (prop_ret != NULL) + XFree(prop_ret); + + *ret_buf = (char *)buffer; + return byte_offset; +} + +size_t +property_get_window_list(char **window_list) +{ + size_t len = property_get_data(DefaultRootWindow(DISP), + atom_xdvi_windows(), window_list, + XGetWindowProperty); + if (len == 0) { + TRACE_CLIENT((stderr, "No \"xdvi windows\" property found")); + return 0; + } + + if (len % 4 != 0) { + TRACE_CLIENT((stderr, "\"XDVI_WINDOWS\" property had incorrect size; deleting it.")); + XDeleteProperty(DISP, DefaultRootWindow(DISP), atom_xdvi_windows()); + return 0; + } + return len; +} + +void +set_window_id(Window w, unsigned char *data) +{ +#if WORDS_BIGENDIAN + data[0] = (unsigned int)w >> 24; + data[1] = (unsigned int)w >> 16; + data[2] = (unsigned int)w >> 8; + data[3] = (unsigned int)w; +#else + data[0] = (unsigned int)w; + data[1] = (unsigned int)w >> 8; + data[2] = (unsigned int)w >> 16; + data[3] = (unsigned int)w >> 24; +#endif +} + +/** + ** set_dvi_property sets the appropriate property for the main + ** window (used in source special handoff). + **/ + +void +set_dvi_property(void) +{ + XChangeProperty(DISP, XtWindow(globals.widgets.top_level), atom_dvi_file(), atom_dvi_file(), + 8, PropModeReplace, (unsigned char *)dvi_property, dvi_property_length); +} + + +/* + * Delete all occurences of window w from the window list property. Then, + * if `prepend' is true, prepend the window ID to the existing list. + */ +void +update_window_property(Window w, Boolean prepend) +{ + char *wlist; + size_t wlist_len; + char *wlist_end; + char *wp; +#if 0 + int i; +#endif /* 0 */ + + /* this allocates wlist */ + if ((wlist_len = property_get_window_list(&wlist)) == 0) + return; + + /* Loop over list of windows. */ + wlist_end = wlist + wlist_len; + +#if 0 + for (i = 0, wp = wlist; wp < wlist_end; wp += 4, i++) { + fprintf(stderr, "WIN %d: %08lx; len: %d\n", i, get_window_id(wp), wlist_len); + } +#endif /* 0 */ + + for (wp = wlist; wp < wlist_end; wp += 4) { + if (get_window_id(wp) == w) { /* match, remove our ID */ + wlist_len -= 4; + wlist_end -= 4; + memmove(wp, wp + 4, wlist_end - wp); + wp -= 4; /* new item is now at wp; don't skip it in next iteration */ + } + } + + if (prepend) { /* add our ID again to front */ +#ifdef WORD64 + unsigned char data[4]; + set_window_id(w, data); +#else + xuint32 data = w; +#endif + /* Note: no need to realloc wlist, since the original length + was sufficient for all elements. + */ + memmove(wlist + 4, wlist, wlist_len); + wlist_len += 4; + memcpy(wlist, &data, 4); + } + + if (wlist_len == 0) + XDeleteProperty(DISP, DefaultRootWindow(DISP), + atom_xdvi_windows()); + else + XChangeProperty(DISP, DefaultRootWindow(DISP), + atom_xdvi_windows(), atom_xdvi_windows(), 32, + PropModeReplace, (unsigned char *)wlist, + wlist_len / 4); + + XFlush(DISP); +} + +void +property_initialize(void) +{ + size_t i; + +#if XlibSpecificationRelease >= 6 + if (!XInternAtoms(DISP, atom_names, XtNumber(atom_names), False, atoms)) + XDVI_FATAL((stderr, "XtInternAtoms failed.")); +#else + for (i = 0; i < XtNumber(atom_names); i++) { + if ((atoms[i] = XInternAtom(DISP, atom_names[i], False)) == None) + XDVI_FATAL((stderr, "XtInternAtoms failed.")); + } +#endif + + if (globals.debug & DBG_CLIENT) { + for (i = 0; i < XtNumber(atom_names); i++) + TRACE_CLIENT((stderr, "Atom(%s) = %lu", atom_names[i], atoms[i])); + } +} + + +Atom atom_xdvi_windows(void) +{ + return atoms[0]; +} + +Atom atom_dvi_file(void) +{ + return atoms[1]; +} + +Atom atom_src_goto(void) +{ + return atoms[2]; +} + +Atom atom_reload(void) +{ + return atoms[3]; +} + +Atom atom_newdoc(void) +{ + return atoms[4]; +} + +Atom atom_newpage(void) +{ + return atoms[5]; +} + +Atom atom_raise(void) +{ + return atoms[6]; +} + +Atom atom_find_string(void) +{ + return atoms[7]; +} + +Atom atom_reread_prefs(void) +{ + return atoms[8]; +} + +/* + * Syntetisize a button press object for the `button' passed as + * the second argument. + */ +void +synthetisize_event(XEvent *ev, Widget button) +{ + memset(ev, 0, sizeof(XButtonPressedEvent)); + ev->type = ButtonPress; + ev->xbutton.serial = 1; + ev->xbutton.send_event = True; + ev->xbutton.button = 1; + ev->xbutton.display = XtDisplayOfObject(button); + ev->xbutton.window = XtWindowOfObject(button); +} + +#ifdef MOTIF +int xm_get_height(Widget w) +{ + int ret_h = 0; + static Arg args = { XtNheight, (XtArgVal)0 }; + ASSERT(w != NULL, "widget in xm_get_width mustn't be NULL!"); + + args.value = (XtArgVal)&ret_h; + XtGetValues(w, &args, 1); + TRACE_GUI((stderr, "xm_get_height: %d", ret_h)); + return ret_h; +} + +int xm_get_width(Widget w) +{ + int ret_w = 0; + static Arg args = { XtNwidth, (XtArgVal)0 }; + ASSERT(w != NULL, "widget in xm_get_width mustn't be NULL!"); + + args.value = (XtArgVal)&ret_w; + XtGetValues(w, &args, 1); + TRACE_GUI((stderr, "xm_get_width: %d", ret_w)); + return ret_w; +} + +/* + * Get pixel from color `colorname'. We try to keep this as + * high-level as possible, with simple black as fallback. +*/ +void +str_to_pixel(Widget w, const char *colorname, Pixel *ret) +{ + XrmValue from, to; + + from.addr = (char *)colorname; + from.size = strlen(from.addr) + 1; + to.addr = (XtPointer)ret; + to.size = sizeof(Pixel); + if (!XtConvertAndStore(w, XtRString, &from, XtRPixel, &to)) { + fprintf(stderr, "String to pixel conversion failed for %s!\n", colorname); + from.addr = (char *)"black"; + from.size = strlen(from.addr) + 1; + to.addr = (XtPointer)ret; + to.size = sizeof(Pixel); + XtConvertAndStore(w, XtRString, &from, XtRPixel, &to); + } +} + +/* + * Convert pixel value `pix' to color name passed as str, + * of length len, or `black' if conversion failed. + * -- FIXME: This is broken!!! +*/ +void +pixel_to_str(Widget w, Pixel pix, char *str, size_t len) +{ + XrmValue from, to; + + from.addr = (XtPointer)&pix; + from.size = sizeof(Pixel); + to.addr = str; + to.size = len; + + if (!XtConvertAndStore(w, XtRString, &from, XtRPixel, &to)) { + fprintf(stderr, "Pixel to String conversion failed for %ld!\n", pix); + sprintf(str, "white"); + } +} + +/* Free color, and initialize it anew with pixel value `pix' */ +void +pixel_to_color(Pixel pix, XColor *color, Display *display, Colormap colormap) +{ + XColor test; + test.pixel = pix; + XQueryColor(display, colormap, &test); + + color->red = test.red; + color->green = test.green; + color->blue = test.blue; + + if (!XAllocColor(display, colormap, color)) { + fprintf(stderr, "Fatal: Couldn't XAllocColor!"); + exit(1); + } +} +#endif /* MOTIF */ + +/* helper routine for get_matching_parent() */ +static Widget +matches_parent(Widget w, const char *name) +{ + for (; w != NULL; w = XtParent(w)) { + char *ptr; + ptr = XtName(w); + TRACE_GUI((stderr, "parent: %s", ptr == NULL ? "" : ptr)); + if (ptr != NULL && strcmp(ptr, name) == 0) { /* found */ + TRACE_GUI((stderr, "match!")); + break; + } + } + TRACE_GUI((stderr, "returning: %p (0x%lx)", (void *)w, w ? XtWindow(w) : 0)); + return w; +} + +/* Traverse widget hieararchy upwards until a widget matches + a name in the (NULL-terminated) list fmt, or Widget d (`default') + if none is found. +*/ +Widget +get_matching_parent(Widget w, Widget d, const char *fmt, ...) +{ + Widget parent = d; + const char *str = fmt; + + va_list argp; + va_start(argp, fmt); + + TRACE_GUI((stderr, "get_matching_parent of %p (0x%lx)", (void *)w, XtWindow(w))); + while (str != NULL) { + Widget p; + if ((p = matches_parent(w, str)) != NULL) { + parent = p; + break; + } + str = va_arg(argp, char *); + } + + va_end(argp); + + return parent; +} + +/* Return True if p is a parent of widget w, stopping (and returning FALSE) + if s is reached (which should be the toplevel window of the hierarchy). +*/ +Boolean +widget_is_parent(Widget w, Widget p, Widget s) +{ + Widget curr = XtParent(w); + while (curr != NULL && curr != s) { + fprintf(stderr, "Comparing: %p - %p\n", (void *)curr, (void *)p); + if (curr == p) + return True; + curr = XtParent(curr); + } + return False; +} + +/* + Adjust height in a NULL-terminated list of widgets. For Motif, + this works better than the following adjust_vertically(). +*/ +void +adjust_heights(Widget w, ...) +{ + va_list ap; + Widget curr; + Dimension h, max; + + ASSERT(w != NULL, "Must have at least one element in va_list for adjust_heights!"); + +#if MOTIF +#define HEIGHT XmNheight +#else +#define HEIGHT XtNheight +#endif + + /* initialize maximum */ + XtVaGetValues(w, HEIGHT, &max, NULL); + + /* get maximum height */ + va_start(ap, w); + while ((curr = va_arg(ap, Widget)) != NULL) { + XtVaGetValues(curr, HEIGHT, &h, NULL); + if (h > max) + max = h; + } + va_end(ap); + + /* set maximum height for all widgets */ + XtVaSetValues(w, HEIGHT, max, NULL); + + va_start(ap, w); + while ((curr = va_arg(ap, Widget)) != NULL) + XtVaSetValues(curr, HEIGHT, max, NULL); + va_end(ap); + +#undef HEIGHT + +} + +/* + Adjust height in a NULL-terminated list of widgets. For Motif, + this works better than the following adjust_vertically(). +*/ +void +adjust_heights_min(Widget w, ...) +{ + va_list ap; + Widget curr; + Dimension h, min; + + ASSERT(w != NULL, "Must have at least one element in va_list for adjust_heights!"); + +#if MOTIF +#define HEIGHT XmNheight +#else +#define HEIGHT XtNheight +#endif + + /* initialize minimum */ + XtVaGetValues(w, HEIGHT, &min, NULL); + + /* get minimum height */ + va_start(ap, w); + while ((curr = va_arg(ap, Widget)) != NULL) { + XtVaGetValues(curr, HEIGHT, &h, NULL); + if (h < min) + min = h; + } + va_end(ap); + + /* set maximum height for all widgets */ + XtVaSetValues(w, HEIGHT, min, NULL); + + va_start(ap, w); + while ((curr = va_arg(ap, Widget)) != NULL) + XtVaSetValues(curr, HEIGHT, min, NULL); + va_end(ap); + +#undef HEIGHT + +} + +/* adjust two widgets vertically */ +void +adjust_vertically(Widget w1, Widget w2, int default_dist) +{ + Dimension h1, h2; +#if MOTIF + XtVaGetValues(w1, XmNheight, &h1, NULL); + XtVaGetValues(w2, XmNheight, &h2, NULL); + XtVaSetValues(w1, XmNtopOffset, default_dist + (h2 - h1) / 2, NULL); + /* XtVaSetValues(w2, XmNtopOffset, default_dist + (h2 - h1) / 2, NULL); */ +#else + XtVaGetValues(w1, XtNheight, &h1, NULL); + XtVaGetValues(w2, XtNheight, &h2, NULL); + XtVaSetValues(w1, XtNvertDistance, default_dist + (h2 - h1) / 2, NULL); + XtVaSetValues(w2, XtNvertDistance, default_dist + (h2 - h1) / 2, NULL); +#endif /* MOTIF */ +} + + +/* + * This is a hack to block further processing of some events on widgets: + * Add as an event handler for all mouse/key events for a specific widget. + */ +void +block_event_callback(Widget w, XtPointer client_data, + XEvent *ev, Boolean *cont) +{ + UNUSED(w); + UNUSED(client_data); + UNUSED(ev); + + /* Don't propagate this event further down... */ + *cont = False; + + return; +} + + +/* Get a widget with `name' somewhere in the widget hierarchy below `parent' + (matching is done against `*name') and return it in `ret'. + If `report_error' is True, a warning message is popped up if the widget isn't found. +*/ +Boolean +get_widget_by_name(Widget *ret, Widget parent, const char *name, Boolean report_error) +{ + char buf[1024]; + Widget test; + +/* if (parent == 0 || !XtIsManaged(parent)) { */ +/* fprintf(stderr, "Widget %p not managed!\n", parent); */ +/* return False; */ +/* } */ + + if (strlen(name) > 1023) { + popup_message(globals.widgets.top_level, + MSG_ERR, + REPORT_XDVI_BUG_TEMPLATE, + "Widget name `%s' too long, couldn't get parent", name); + return False; + } + + buf[0] = '*'; /* add wildcard to also match paths */ + strcpy(buf + 1, name); + + if ((test = XtNameToWidget(parent, buf)) != NULL) { + *ret = test; + return True; + } + else { + if (report_error) + popup_message(globals.widgets.top_level, + MSG_ERR, + REPORT_XDVI_BUG_TEMPLATE, + "XtNameToWidget failed for `%s', parent `%s'", name, XtName(parent)); + return False; + } +} + +void +unexpected_widget_in_callback(Widget w, const char *callback) +{ + ASSERT(w != NULL, "Widget mustn't be NULL!"); + popup_message(globals.widgets.top_level, + MSG_ERR, + REPORT_XDVI_BUG_TEMPLATE, + "Unexpected widget `%s' in callback `%s'", + XtName(w), callback); +} + +static XrmDatabase m_user_db = NULL; + +/* + * Merge the resource `name' with a value specified by `fmt' into + * the database `db' (if db == NULL, use m_user_db). + */ +void +store_preference(XrmDatabase *db, const char *name, const char *fmt, ...) +{ + size_t offset = strlen("xdvi."); + size_t name_len = strlen(name); + char *name_buf = xmalloc(name_len + offset + 1); + char *buf = NULL; + XrmDatabase tmp_db = NULL; + + if (db == NULL) + db = &m_user_db; + + XDVI_GET_STRING_ARGP(buf, fmt); + + memcpy(name_buf, "xdvi.", offset); + strcpy(name_buf + offset, name); + + TRACE_GUI((stderr, "storing resource: `%s: %s'", name_buf, buf)); + XrmPutStringResource(&tmp_db, name_buf, buf); + XrmMergeDatabases(tmp_db, db); + + free(buf); + free(name_buf); +} + +void +merge_into_user_db(XrmDatabase db) +{ + XrmMergeDatabases(db, &m_user_db); +} + +const char *const xdvirc_filename = ".xdvirc"; +const char *const xdvirc_signature_line = "!!! ~/.xdvirc, used by xdvi(1) to save user preferences.\n"; +const char *const xdvirc_header = "" + "!!!\n" + "!!! Do not edit this file, it will be overwritten by xdvi.\n" + "!!! This file contains resources that have been set via the\n" + "!!! menus/dialogs. The contents of this file will override\n" + "!!! the entries in your ~/.Xdefaults file (but not the command\n" + "!!! line arguments passed to xdvi). Remove this file\n" + "!!! if you want to get rid of all these customizations,\n" + "!!! or start xdvi with the `-q' option to ignore this file.\n" + "!!!\n"; + +static char * +get_xdvirc_path(const char *basename) +{ + const char *homedir; + size_t len; + char *str; + + if (basename == NULL) + return NULL; + + homedir = getenv("HOME"); + len = strlen(homedir) + strlen(basename) + 2; /* 1 more for '/' */ + str = xmalloc(len); + + sprintf(str, "%s/%s", homedir, basename); + return str; +} + +static void +save_geometry(void) +{ + int x_off, y_off; + Dimension w, h; + Window dummy; +/* char *geom_str; */ + + (void)XTranslateCoordinates(DISP, XtWindow(globals.widgets.top_level), + RootWindowOfScreen(SCRN), + 0, 0, + &x_off, &y_off, + &dummy); + XtVaGetValues(globals.widgets.top_level, + XtNwidth, &w, + XtNheight, &h, +#ifdef MOTIF +/* XmNgeometry, &geom_str, */ +#endif + NULL); + TRACE_GUI((stderr, "geometry: %dx%d+%d+%d", w, h, x_off, y_off)); + + store_preference(NULL, "windowSize", "%dx%d", w, h); +} + +/* Save user preferences to ~/.xdvirc. If `backup_only' is True, + it only writes to ~/.xdvirc.tmp and does not remove this temporary + file (this is used for synchronization between several xdvi instances). +*/ +Boolean +save_user_preferences(Boolean full_save) +{ + char testbuf[1024]; + char *xdvirc_name; + char *tmpname; + FILE *from_fp, *to_fp; + int fd; + + if (resource.no_init_file + || m_user_db == NULL) /* nothing to do */ + return True; + + if (resource.remember_windowsize) + save_geometry(); + + xdvirc_name = get_xdvirc_path(xdvirc_filename); + + if ((to_fp = fopen(xdvirc_name, "r")) != NULL) { + TRACE_GUI((stderr, "~/.xdvirc exists, checking file contents ...")); + if (fgets(testbuf, 1024, to_fp) != NULL && + memcmp(testbuf, xdvirc_signature_line, strlen(xdvirc_signature_line)) != 0) { + popup_message(globals.widgets.top_level, + MSG_WARN, + "Xdvi uses the file ~/.xdvirc to save the preferences set via " + "the menu bar or the preferences dialog (in the Motif version only). " + "To avoid overwriting files created by the user, the first line of the " + "file is compared with a special signature line. If that signature line " + "is not found, the preferences won't be written. Your file doesn't seem " + "to contain that signature line. You should move the file to a safe location, " + "and then try to quit xdvi again.", + /* message */ + "The file `%s' was apparently not written by xdvi(k). " + "Please move or delete this file first, then try to exit xdvi again. ", + xdvirc_name); + return False; + } + fclose(to_fp); + } + + /* don't use xdvi_temp_fd here, since XrmPutFileDatabase() + closes the FILE*, creating a temp race */ + tmpname = xstrdup(xdvirc_name); + tmpname = xstrcat(tmpname, ".tmp"); + + /* since XrmPutFileDatabase doesn't give a useful error message if it fails, + check that creating the file works beforehand. The file is created with 0600 permissions. */ + if ((fd = try_open_mode(tmpname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) < 0) { + XDVI_ERROR((stderr, "Could not save preferences!\nOpening %s for writing failed: %s", tmpname, strerror(errno))); + return True; + } + close(fd); + + XrmPutFileDatabase(m_user_db, tmpname); + + if (full_save) { + if ((from_fp = try_fopen(tmpname, "r")) == NULL) { + XDVI_ERROR((stderr, "Could not save preferences!\nOpening %s for reading failed: %s", tmpname, strerror(errno))); + return True; + } + + /* again, create the file with 600 permissions */ + if ((fd = try_open_mode(xdvirc_name, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) { + XDVI_ERROR((stderr, "Could not save preferences!\nOpening %s for writing failed: %s", xdvirc_name, strerror(errno))); + return True; + } + + if ((to_fp = fdopen(fd, "w")) == NULL) { + XDVI_ERROR((stderr, "Could not save preferences!\nfdopen for %s for writing failed: %s", xdvirc_name, strerror(errno))); + return True; + } + + if (fputs(xdvirc_signature_line, to_fp) == EOF + || fputs(xdvirc_header, to_fp) == EOF + || !copy_fp(from_fp, to_fp)) { + XDVI_ERROR((stderr, "Could not save preferences!\nError writing to %s: %s", xdvirc_name, strerror(errno))); + } + + fclose(from_fp); + fclose(to_fp); + } + + free(xdvirc_name); + + if (full_save) + unlink(tmpname); + free(tmpname); + + return True; +} + +/* + * Read the user preferences from xdvirc_filename and merge them into the + * current resource database *and* into m_user_db so that all of them are + * saved again when xdvi exits. + */ +void +read_user_preferences(Widget toplevel, const char *filename) +{ + char *fullpath; + XrmDatabase db; +#if XtSpecificationRelease == 4 + XrmDatabase file_db; +#endif + + fullpath = get_xdvirc_path(filename); + TRACE_GUI((stderr, "Reading resources from `%s'", fullpath)); + db = XtDatabase(XtDisplay(toplevel)); + +#if XtSpecificationRelease == 4 + file_db = XrmGetFileDatabase(fullpath); + XrmMergeDatabases(file_db, &db); + XrmMergeDatabases(file_db, &m_user_db); +#else /* Xt >= X11R5 */ + XrmCombineFileDatabase(fullpath, &db, True); + XrmCombineFileDatabase(fullpath, &m_user_db, True); +#endif + free(fullpath); +} + + +/* + * Routines for running as source-special client. + */ + +static unsigned long xdvi_next_request = 0; +static int xerrno; +static int (*XdviOldErrorHandler)(Display *, XErrorEvent *); + +static int +XdviErrorHandler(Display *d, XErrorEvent *event) +{ + if (event->serial != xdvi_next_request || event->error_code != BadWindow) + return XdviOldErrorHandler(d, event); + + xerrno = 1; + return 0; +} + +static int +XdviGetWindowProperty(Display *display, + Window w, + Atom property, + long long_offset, + long long_length, + Bool delete, + Atom req_type, + Atom *actual_type_return, + int *actual_format_return, + unsigned long *nitems_return, + unsigned long *bytes_after_return, + unsigned char **prop_return) +{ + int retval; + + xdvi_next_request = NextRequest(display); + xerrno = 0; + + retval = XGetWindowProperty(display, w, property, long_offset, + long_length, delete, req_type, + actual_type_return, actual_format_return, + nitems_return, bytes_after_return, prop_return); + + return (xerrno != 0 ? BadWindow : retval); +} + +/* helper function to set a string property of type `prop' for window `win' */ +void +set_string_property(const char *str, Atom prop, Window win) +{ + XChangeProperty(DISP, win, prop, prop, 8, PropModeReplace, + (const unsigned char *)str, strlen(str)); + XFlush(DISP); /* necessary to get the property set */ +} + +/* + * Check for another running copy of xdvi. If same_file is true, return + * the window ID of that other instance only if it has currently loaded the + * same file; else, return 0. + * If same_file is false, return the first valid xdvi window ID. + */ +Window +get_xdvi_window_id(Boolean same_file, property_cbT callback) +{ + char *window_list; + size_t window_list_len; + char *window_list_end; + char *wp; + char *p; + Boolean need_rewrite = False; + Window ret_window = 0; + + /* + * Get window list. Copy it over (we'll be calling property_get_data() + * again). + */ + if ((window_list_len = property_get_window_list(&p)) == 0) + return 0; + + window_list = xmalloc(window_list_len); + memcpy(window_list, p, window_list_len); + + XdviOldErrorHandler = XSetErrorHandler(XdviErrorHandler); + + /* Loop over list of windows. */ + + window_list_end = window_list + window_list_len; + TRACE_CLIENT((stderr, "My property: `%s'", dvi_property)); + + for (wp = window_list; wp < window_list_end; wp += 4) { + Window w; + char *buf_ret; + size_t len; + + w = get_window_id(wp); + + TRACE_CLIENT((stderr, "Checking window %08lx", w)); + + len = property_get_data(w, atom_dvi_file(), &buf_ret, + XdviGetWindowProperty); + + if (len == 0) { + /* not getting back info for a window usually indicates + that the application the window had belonged to had + been killed with signal 9 + */ + TRACE_CLIENT((stderr, "Window %08lx: doesn't exist any more, deleting", w)); + window_list_len -= 4; + window_list_end -= 4; + memmove(wp, wp + 4, window_list_end - wp); + wp -= 4; /* new item is now at wp; don't skip it in next iteration */ + need_rewrite = True; + continue; + } + else { /* window still alive */ + if (globals.debug & DBG_CLIENT) { +#if 0 + unsigned long ino; + int i; + + ino = 0; + for (i = 7; i >= 0; --i) + ino = (ino << 8) | (unsigned char)(buf_ret[i]); +#endif + TRACE_CLIENT((stderr, "Window %08lx: property: `%s'", w, buf_ret)); + } + + /* invoke callback if given */ + if (callback != NULL) { + callback(w); + } + + if (!same_file && ret_window == 0) { + ret_window = w; + if (callback == 0) /* can return early */ + break; + } + else if (strcmp(buf_ret, dvi_property) == 0 && ret_window == 0) { /* match */ + ret_window = w; + if (callback == 0) /* can return early */ + break; + } + } + } + + XSetErrorHandler(XdviOldErrorHandler); + + if (need_rewrite) + XChangeProperty(DISP, DefaultRootWindow(DISP), + atom_xdvi_windows(), atom_xdvi_windows(), 32, + PropModeReplace, (unsigned char *)window_list, + window_list_len / 4); + + return ret_window; +} + +Boolean +clip_region(int *x, int *y, int *w, int *h) +{ +#if 0 + fprintf(stderr, "globals.win_expose.min_x: %d, globals.win_expose.max_x: %d, " + "globals.win_expose.min_y: %d, globals.win_expose.max_y: %d\n", + globals.win_expose.min_x, globals.win_expose.max_x, + globals.win_expose.min_y, globals.win_expose.max_y); +#endif + /* check for <= so that we also return false if *w or *h == 0 */ + if (*x + *w <= globals.win_expose.min_x + || *x >= globals.win_expose.max_x + || *y + *h <= globals.win_expose.min_y + || *y >= globals.win_expose.max_y + /* extra protection agains *w or *h == 0; don't know if this can actually happen ... */ + || globals.win_expose.max_y == globals.win_expose.min_y + || globals.win_expose.max_x == globals.win_expose.min_x) { + return False; + } + if (*x < globals.win_expose.min_x) { + *w -= globals.win_expose.min_x - *x; + *x = globals.win_expose.min_x; + } + if (*x + *w > globals.win_expose.max_x) { + *w = globals.win_expose.max_x - *x; + } + if (*y < globals.win_expose.min_y) { + *h -= globals.win_expose.min_y - *y; + *y = globals.win_expose.min_y; + } + if (*y + *h > globals.win_expose.max_y) { + *h = globals.win_expose.max_y - *y; + } + return True; +} + +Boolean +clip_region_to_rect(XRectangle *rect) +{ + int x = rect->x; + int y = rect->y; + int w = rect->width; + int h = rect->height; + Boolean ret = clip_region(&x, &y, &w, &h); + if (ret) { + rect->x = x; + rect->y = y; + rect->width = w; + rect->height = h; + } + return ret; +} + -- cgit v1.2.3