1 /*
2 * Copyright (c) 2004 Stefan Ulrich
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to
6 * deal in the Software without restriction, including without limitation the
7 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 * sell copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 * PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
18 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20 * OTHER DEALINGS IN THE SOFTWARE.
21 *
22 */
23
24 /*
25 * `Topics' window framework used by help and preferences windows.
26 * Contains a list of topics on the left-hand side, and corresponding
27 * contents on the right-hand side.
28 */
29
30 #include "xdvi-config.h"
31 #include "xdvi.h"
32
33 #if MOTIF
34 # include <Xm/Xm.h>
35 # include <Xm/Form.h>
36 # include <Xm/DialogS.h>
37 # include <Xm/PushB.h>
38 # include <Xm/Frame.h>
39 # include <Xm/PanedW.h>
40 # include <Xm/LabelG.h>
41 # include <Xm/Label.h>
42 # include <Xm/Protocols.h>
43 # include <Xm/List.h>
44 # include <Xm/AtomMgr.h>
45 #else /* MOTIF */
46 # include <X11/Xaw/Paned.h>
47 # include <X11/Xaw/Box.h>
48 # include <X11/Xaw/Form.h>
49 # include <X11/Xaw/Cardinals.h>
50 # include <X11/Xaw/Command.h>
51 # include <X11/Xaw/List.h>
52 #endif /* MOTIF */
53
54 #include "version.h"
55 #include "topic-window.h"
56 #include "string-utils.h"
57 #include "util.h"
58 #include "x_util.h"
59
60 #if MOTIF
61
62 /* special formatting for headings in right window, if desired */
63 const char *const TAG_NORMAL = "NORMAL";
64 const char *const TAG_TOPIC_HEADING = "TOPIC_HEADING";
65 const char *const TAG_TOPIC_LABEL = "TOPIC_LABEL";
66
67 # define SHELL_WIDGET_CLASS xmDialogShellWidgetClass
68 # define PANED_WIDGET_CLASS xmPanedWindowWidgetClass
69 # define FORM_WIDGET_CLASS xmFormWidgetClass
70
71 # define FORM_ARGS XmNhorizontalSpacing, 10, \
72 NULL
73 # define HELP_SHELL_ARGS XmNdeleteResponse, XmDO_NOTHING, \
74 XtNmappedWhenManaged, False, \
75 NULL
76 # define HELP_PANED_ARGS XmNsashWidth, 1, \
77 XmNuserData, info, \
78 XmNsashHeight, 1, \
79 NULL
80 # define LEFT_FORM_ARGS XmNtopAttachment, XmATTACH_FORM, \
81 XmNleftAttachment, XmATTACH_FORM, \
82 XmNbottomAttachment, XmATTACH_FORM, \
83 XmNtopOffset, 9, \
84 XmNbottomOffset, 10, \
85 NULL
86 # define RIGHT_FORM_ARGS XmNtopAttachment, XmATTACH_FORM, \
87 XmNleftAttachment, XmATTACH_WIDGET, \
88 XmNleftWidget, left_form, \
89 XmNrightAttachment, XmATTACH_FORM, \
90 XmNbottomAttachment, XmATTACH_FORM, \
91 XmNbottomOffset, 10, \
92 NULL
93 # define ACTION_AREA_ARGS NULL
94
95 #else /* MOTIF */
96
97 # define SHELL_WIDGET_CLASS transientShellWidgetClass
98 # define PANED_WIDGET_CLASS panedWidgetClass
99 # define FORM_WIDGET_CLASS formWidgetClass
100
101 # define FORM_ARGS XtNdefaultDistance, 14, \
102 NULL
103 # define HELP_SHELL_ARGS XtNx, 60, \
104 XtNy, 80, \
105 XtNtransientFor, globals.widgets.top_level, \
106 XtNtranslations, xlats, \
107 XtNtransientFor, parent, \
108 XtNallowShellResize, False, \
109 NULL
110 # define HELP_PANED_ARGS NULL
111 # define LEFT_FORM_ARGS XtNtop, XtChainTop, \
112 XtNbottom, XtChainBottom, \
113 XtNleft, XtChainLeft, \
114 XtNright, XtChainLeft, \
115 XtNborderWidth, 0, \
116 NULL
117 # define RIGHT_FORM_ARGS XtNfromHoriz, left_form, \
118 XtNtop, XtChainTop, \
119 XtNbottom, XtChainBottom, \
120 XtNleft, XtChainLeft, \
121 XtNright, XtChainRight, \
122 XtNborderWidth, 0, \
123 NULL
124 # define ACTION_AREA_ARGS XtNdefaultDistance, 6, \
125 XtNshowGrip, False, \
126 XtNskipAdjust, True, \
127 NULL
128 #endif /* MOTIF */
129
130
131 static void
ok_cb(Widget w,XtPointer client_data,XtPointer call_data)132 ok_cb(Widget w, XtPointer client_data, XtPointer call_data)
133 {
134 struct topic_info *info = (struct topic_info *)client_data;
135
136 UNUSED(w);
137 UNUSED(call_data);
138
139 ASSERT(info != NULL, "No info passed to callback!");
140
141 if (info->ok_callback != NULL)
142 info->ok_callback(info);
143 XtPopdown(info->shell);
144 }
145
146 static void
cancel_cb(Widget w,XtPointer client_data,XtPointer call_data)147 cancel_cb(Widget w, XtPointer client_data, XtPointer call_data)
148 {
149 struct topic_info *info = (struct topic_info *)client_data;
150
151 UNUSED(w);
152 UNUSED(call_data);
153
154 ASSERT(info != NULL, "No info passed to callback!");
155
156 if (info->cancel_callback != NULL)
157 info->cancel_callback(info);
158 XtPopdown(info->shell);
159 }
160
161 #if MOTIF
162 /*
163 * We are overriding this since otherwise the Text widget would attempt to
164 * first move the invisible cursor instead of the scroll bars.
165 */
166 /* FIXME: This gives a warning:
167 Warning: Actions not found: scroll-one-line-down, scroll-one-line-up
168 */
169 /* "<Key>osfUp: scroll-one-line-down()\n" */
170 /* "<Key>osfDown: scroll-one-line-up()\n" */
171 #endif
172
173 #if MOTIF
174 static XmString
175 #else
176 static char *
177 #endif
create_label_string(const char * title,const char * subtitle)178 create_label_string(const char *title, const char *subtitle)
179 {
180 #if MOTIF
181 XmString label;
182 if (subtitle == NULL) { /* simple bold label */
183 label = XmStringCreate((char *)title, (char *)TAG_TOPIC_LABEL);
184 }
185 else { /* two-part title with subheading */
186 XmString s1, s2, s3, s4;
187 s1 = XmStringCreate((char *)title, (char *)TAG_TOPIC_HEADING);
188 s2 = XmStringCreate((char *)" ", (char *)TAG_NORMAL);
189 s3 = XmStringCreate((char *)subtitle, (char *)TAG_NORMAL);
190 s4 = XmStringConcat(s1, s2);
191 label = XmStringConcat(s4, s3);
192 XmStringFree(s1);
193 XmStringFree(s2);
194 XmStringFree(s3);
195 XmStringFree(s4);
196 }
197 #else
198 char *label = xstrdup(title);
199 if (subtitle != NULL && strlen(subtitle) > 0) {
200 label = xstrcat(label, " - ");
201 label = xstrcat(label, subtitle);
202 }
203 #endif
204 return label;
205 }
206
207 static Widget
create_label_widget(Widget parent,const char * name,struct topic_info * info)208 create_label_widget(Widget parent, const char *name, struct topic_info *info)
209 {
210 Widget label;
211
212 #if MOTIF
213
214 XmString label_string = create_label_string(info->items[0].topic, info->items[0].title);
215 label = XtVaCreateWidget(name, xmLabelWidgetClass, parent,
216 XmNtopAttachment, XmATTACH_FORM,
217 XmNtopOffset, 14,
218 XmNleftAttachment, XmATTACH_FORM,
219 XmNrightAttachment, XmATTACH_FORM,
220 XmNlabelString, label_string,
221 XmNalignment, XmALIGNMENT_BEGINNING,
222 NULL);
223 XmStringFree(label_string);
224
225 #else /* MOTIF */
226
227 char *label_string = create_label_string(info->items[0].topic, info->items[0].title);
228 label = XtVaCreateWidget(name, labelWidgetClass, parent,
229 XtNlabel, label_string,
230 XtNborderWidth, 0,
231 /* XtNinternalHeight, 2, */
232 XtNjustify, XtJustifyLeft,
233 NULL);
234 free(label_string);
235
236 #endif /* MOTIF */
237
238 return label;
239 }
240
241 void
select_topic(struct topic_info * info,size_t idx)242 select_topic(struct topic_info *info, size_t idx)
243 {
244 #if MOTIF
245 XmString label;
246 #else
247 char *label;
248 #endif
249
250 /* change the heading in right window */
251 label = create_label_string(info->items[idx].topic, info->items[idx].title);
252 XtVaSetValues(info->topic_label,
253 #if MOTIF
254 XmNlabelString,
255 #else
256 XtNlabel,
257 #endif
258 label, NULL);
259
260 #if MOTIF
261 XmStringFree(label);
262 XmListSelectPos(info->topics_list, idx + 1, False);
263 #else
264 free(label);
265 XawListHighlight(info->topics_list, idx);
266 #endif
267
268 if (info->curr_selected != 0) {
269 XtUnmanageChild(info->curr_selected);
270 }
271 else {
272 /* Note: doesn't matter if not managed yet */
273 /* XtUnmanageChild(info->items[0].widget); */
274 }
275
276 XtManageChild(info->items[idx].widget);
277 info->curr_selected = info->items[idx].widget;
278
279 #if MOTIF
280 XmUpdateDisplay(info->shell);
281 #endif
282 }
283
284 static void
select_topic_cb(Widget w,XtPointer client_data,XtPointer call_data)285 select_topic_cb(Widget w, XtPointer client_data, XtPointer call_data)
286 {
287 struct topic_info *info = (struct topic_info *)client_data;
288 size_t idx;
289
290 #if MOTIF
291 idx = ((XmListCallbackStruct *)call_data)->item_position - 1;
292 #else
293 idx = ((XawListReturnStruct *)call_data)->list_index;
294 #endif
295 UNUSED(w);
296
297 /* ASSERT(idx < info->items_size, "list position exceeds items_size!"); */
298
299 select_topic(info, idx);
300 }
301
302 static Widget
create_button(Widget parent,const char * name,Boolean show_as_default,Boolean left_position)303 create_button(Widget parent, const char *name, Boolean show_as_default, Boolean left_position)
304 {
305 Widget button;
306 #if MOTIF
307 XmString s1 = XmStringCreateLocalized((char *)name);
308 button = XtVaCreateWidget(name, xmPushButtonWidgetClass, parent,
309 XmNlabelString, s1,
310 XmNdefaultButtonShadowThickness, 1,
311 XmNtopAttachment, XmATTACH_FORM,
312 XmNbottomAttachment, XmATTACH_FORM,
313 XmNmarginWidth, 6,
314 XmNmarginHeight, 4,
315 XmNtopOffset, 10,
316 XmNbottomOffset, 10,
317 NULL);
318 if (left_position)
319 XtVaSetValues(button,
320 XmNleftAttachment, XmATTACH_FORM,
321 XmNleftOffset, 10,
322 NULL);
323 else
324 XtVaSetValues(button,
325 XmNrightAttachment, XmATTACH_FORM,
326 XmNrightOffset, 10,
327 NULL);
328 if (show_as_default)
329 XtVaSetValues(button, XmNshowAsDefault, True, NULL);
330
331 XtManageChild(button);
332 XmStringFree(s1);
333 #else /* MOTIF */
334 UNUSED(show_as_default);
335 button = XtVaCreateManagedWidget(name, commandWidgetClass, parent,
336 XtNtop, XtChainTop,
337 XtNbottom, XtChainBottom,
338 NULL);
339 if (left_position)
340 XtVaSetValues(button,
341 XtNleft, XtChainLeft,
342 XtNright, XtChainLeft,
343 NULL);
344 else
345 XtVaSetValues(button,
346 XtNleft, XtChainRight,
347 XtNright, XtChainRight,
348 NULL);
349 #endif /* MOTIF */
350 return button;
351 }
352
353 static void
close_topic_window(Widget w,XEvent * event,String * params,Cardinal * num_params)354 close_topic_window(Widget w, XEvent *event, String *params, Cardinal *num_params)
355 {
356 Widget button;
357 void *ptr;
358 struct topic_info *info;
359
360 UNUSED(w);
361 UNUSED(event);
362
363 if (*num_params < 1) {
364 XDVI_WARNING((stderr, "Wrong argument number (%d) in callback!", *num_params));
365 return;
366 }
367
368 sscanf(*params, "%p", &ptr);
369 info = (struct topic_info *)ptr;
370
371 /* get a callback to close the button, in decreasing order of preference */
372 if (get_widget_by_name(&button, info->shell, "Cancel", False)
373 || get_widget_by_name(&button, info->shell, "Close", False)
374 || get_widget_by_name(&button, info->shell, "OK", False)) {
375 XtCallCallbacks(button,
376 #if MOTIF
377 XmNactivateCallback,
378 #else
379 XtNcallback,
380 #endif
381 info);
382 }
383 else {
384 XDVI_WARNING((stderr, "No button found for widget %p!\n", (void *)info->shell));
385 }
386 }
387
388 static XtActionsRec popdown_actions[] = {
389 #if !MOTIF
390 { "WM_popdown", close_topic_window },
391 #endif
392 { "close-topic-window", close_topic_window },
393 };
394
395 static Widget
create_list_widget(Widget parent,const char * w_name,struct topic_info * info)396 create_list_widget(Widget parent, const char *w_name, struct topic_info *info)
397 {
398 Widget list;
399 size_t tnum;
400 #if MOTIF
401 XmString *topics = NULL;
402 #else
403 static char **topics = NULL;
404 #endif
405
406 for (tnum = 0; info->items[tnum].topic != NULL; tnum++) {
407 /* 1 more for terminating NULL needed for Xaw */
408 topics = xrealloc(topics, (tnum + 2) * sizeof *topics);
409 #if MOTIF
410 topics[tnum] = XmStringCreateLocalized(info->items[tnum].topic);
411 #else
412 topics[tnum] = xstrdup(info->items[tnum].topic);
413 #endif
414 }
415
416 #ifndef MOTIF
417 /* list needs to be terminated for Xaw */
418 topics[tnum] = NULL;
419 #endif
420
421 #if MOTIF
422 {
423 size_t i;
424 Arg args[20];
425 int n = 0;
426 XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
427 XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
428 XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
429 XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
430 XtSetArg(args[n], XmNlistSpacing, 4); n++;
431 XtSetArg(args[n], XmNlistMarginHeight, 4); n++;
432 XtSetArg(args[n], XmNlistMarginWidth, 4); n++;
433 XtSetArg(args[n], XmNhighlightThickness, 0); n++;
434 XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
435
436 list = XmCreateScrolledList(parent, (char *)w_name, args, n);
437
438 #if defined(__GNUC__) && DEVEL_MODE
439 #warning TODO: make up/down scroll the list, and PgUp/PgDown scroll help text
440 #warning TODO: add wheel mouse bindings
441 /*
442 * also fix bug with arrow keys first moving invisible cursor, then scrollbars
443 */
444 #endif
445
446 XmListDeleteAllItems(list);
447 XmListAddItems(list, topics, tnum, 0);
448
449 for (i = 0; i < tnum; ++i) {
450 XmStringFree(topics[i]);
451 }
452 free(topics);
453 }
454 XtAddCallback(list, XmNbrowseSelectionCallback, select_topic_cb, info);
455 XmListSelectPos(list, 1, False); /* default position */
456 #else /* MOTIF */
457 list = XtVaCreateWidget(w_name, listWidgetClass, parent,
458 XtNlist, topics,
459 XtNdefaultColumns, 1,
460 XtNforceColumns, True,
461 XtNverticalList, True,
462 XtNrowSpacing, 4,
463 XtNheight, 429,
464 NULL);
465 XtAddCallback(list, XtNcallback, select_topic_cb, info);
466 XawListHighlight(list, 0); /* default position */
467 #endif /* MOTIF */
468 XtManageChild(list);
469 return list;
470 }
471
472
473 Widget
create_topic_window(Widget parent,const char * window_title,const char * widget_name,struct topic_info * info,void (* init_items_func)(struct topic_info * info),const char * ok_label,const char * cancel_label)474 create_topic_window(Widget parent,
475 const char *window_title,
476 const char *widget_name,
477 struct topic_info *info,
478 void (*init_items_func)(struct topic_info *info),
479 /* OK button/callbacks */
480 const char *ok_label,
481 /* Cancel button/callbacks (can be NULL) */
482 const char *cancel_label)
483 {
484 Widget topic_window, topics_list, topic_label;
485 Widget pane, form, left_form, right_container_form, right_form;
486 Widget action_area, ok_button, cancel_button;
487 XtTranslations xlats;
488
489 XtAppContext app = NULL;
490 Atom WM_DELETE_WINDOW;
491
492 char *translation_str;
493 size_t i;
494
495 #if !MOTIF
496 translation_str = get_string_va("<Message>WM_PROTOCOLS: WM_popdown(%p)", info);
497 xlats = XtParseTranslationTable(translation_str);
498 free(translation_str);
499 #endif
500
501 topic_window = XtVaCreatePopupShell(widget_name, SHELL_WIDGET_CLASS,
502 parent,
503 XtNtitle, window_title,
504 HELP_SHELL_ARGS);
505
506 #if MOTIF
507 /* make the window manager destroy action pop down the window */
508 WM_DELETE_WINDOW = XmInternAtom(XtDisplay(topic_window), "WM_DELETE_WINDOW", False);
509 XmAddWMProtocolCallback(topic_window, WM_DELETE_WINDOW, cancel_cb, info);
510 #else
511 WM_DELETE_WINDOW = XInternAtom(XtDisplay(topic_window), "WM_DELETE_WINDOW", False);
512 #endif
513 app = XtWidgetToApplicationContext(topic_window);
514 XtAppAddActions(app, popdown_actions, XtNumber(popdown_actions));
515
516 pane = XtVaCreateWidget("topic_pane", PANED_WIDGET_CLASS, topic_window, HELP_PANED_ARGS);
517
518 form = XtVaCreateWidget("form", FORM_WIDGET_CLASS, pane, FORM_ARGS);
519
520 /* left pane for topics selection */
521 left_form = XtVaCreateWidget("left_form", FORM_WIDGET_CLASS, form, LEFT_FORM_ARGS);
522
523 /* right form for topics display */
524 right_container_form = XtVaCreateWidget("right_container_form", FORM_WIDGET_CLASS, form, RIGHT_FORM_ARGS);
525
526 /*
527 Initialize the topic label and title string with dummy values - the real values
528 are only known inside init_items_func(). For Xaw, the size of the widget will be
529 adjusted below (for Motif it's always the full width of the right form, which is
530 what we want).
531 */
532 /* FIXME: Motif label is still chopped off if it's longer than right form -
533 work around by putting longest possible lable here ... */
534 /* Also, not all children will be sized correctly, e.g. the colors - work around
535 by specifying a rather large dummy text ... */
536 info->items[0].topic = "text text text";
537 info->items[0].title = "text text text text text text text text text text text text text text text text text";
538 topic_label = create_label_widget(right_container_form, "topic_label", info);
539 info->topic_label = topic_label;
540
541 right_form = XtVaCreateWidget("right_form", FORM_WIDGET_CLASS,
542 right_container_form,
543 #if MOTIF
544 XmNtopAttachment, XmATTACH_WIDGET,
545 XmNtopWidget, topic_label,
546 XmNtopOffset, 10,
547 XmNleftAttachment, XmATTACH_FORM,
548 XmNrightAttachment, XmATTACH_FORM,
549 XmNbottomAttachment, XmATTACH_FORM,
550 #else
551 XtNborderWidth, 0,
552 XtNfromVert, topic_label,
553 XtNvertDistance, 6,
554 #endif /* MOTIF */
555 NULL);
556 info->right_form = right_form;
557
558 /*
559 Call the init_items_func callback. This will populate the info->items list
560 with the appropriate info, and create the forms (info->items[i].widget)
561 which are the children of right_form (which is accessed inside init_items_func()
562 through info->right_form), and all their subchildren.
563
564 It is important that every child form is already managed inside init_items_func
565 so that the total size of the preferences window is set to the maximum size of
566 the children. (The children will be unmanaged again below). All these widgets
567 are never destroyed, and are managed by demand via the list selection callback
568 (select_topic_cb).
569 */
570 init_items_func(info);
571
572 XtManageChild(topic_label);
573
574 topics_list = create_list_widget(left_form, "topics_list", info);
575 info->topics_list = topics_list;
576
577 translation_str = get_string_va("#override \n"
578 "<Key>q:close-topic-window(%p)\n"
579 #ifdef MOTIF
580 "<Key>osfCancel:close-topic-window(%p)\n"
581 #else
582 "<Key>Escape:close-topic-window(%p)\n"
583 #endif
584 "<Key>Return:close-topic-window(%p)",
585 info, info, info);
586 xlats = XtParseTranslationTable(translation_str);
587 free(translation_str);
588 translation_str = NULL;
589
590 XtOverrideTranslations(pane, xlats);
591 XtOverrideTranslations(topics_list, xlats);
592
593 XtManageChild(left_form);
594
595 XtManageChild(right_form);
596 XtManageChild(right_container_form);
597
598 /* action area at bottom */
599 action_area = XtVaCreateWidget("action_area", FORM_WIDGET_CLASS, pane, ACTION_AREA_ARGS);
600
601 ok_button = create_button(action_area, ok_label, True, True);
602 XtOverrideTranslations(ok_button, xlats);
603
604 if (cancel_label != NULL) {
605 cancel_button = create_button(action_area, cancel_label, False, False);
606 adjust_width(ok_button, cancel_button);
607 #if MOTIF
608 XtAddCallback(cancel_button, XmNactivateCallback, cancel_cb, info);
609 #else
610 XtAddCallback(cancel_button, XtNcallback, cancel_cb, info);
611 #endif
612 XtOverrideTranslations(cancel_button, xlats);
613 }
614
615 XtManageChild(action_area);
616
617 XtManageChild(form);
618
619 XtManageChild(pane);
620
621 XtRealizeWidget(topic_window);
622
623 /* Now unmanage all children of right_form, as described above. */
624 for (i = 0; info->items[i].topic != NULL; i++) {
625 XtUnmanageChild(info->items[i].widget);
626 }
627 info->curr_selected = 0;
628
629 #if MOTIF
630 /* enable OK button */
631 XmProcessTraversal(ok_button, XmTRAVERSE_CURRENT);
632 XtOverrideTranslations(ok_button,
633 XtParseTranslationTable("<Key>Return:ArmAndActivate()\n"
634 "<Key>q:ArmAndActivate()"));
635 XtAddCallback(ok_button, XmNactivateCallback, ok_cb, info);
636 XmAddWMProtocolCallback(topic_window, WM_DELETE_WINDOW, cancel_cb, info);
637 { /* disable resizing of lower part of pane (and button) */
638 Dimension h;
639 XtVaGetValues(ok_button, XmNheight, &h, NULL);
640 XtVaSetValues(action_area,
641 XmNpaneMaximum, h + 20,
642 XmNpaneMinimum, h + 20,
643 NULL);
644 }
645 #else
646 XSetWMProtocols(XtDisplay(topic_window), XtWindow(topic_window), &WM_DELETE_WINDOW, 1);
647 XtAddCallback(ok_button, XtNcallback, ok_cb, info);
648 #endif
649 return topic_window;
650 }
651