home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 1999 mARCH / PCWK3A99.iso / Linux / DDD331 / DDD-3_1_.000 / DDD-3_1_ / ddd-3.1.1 / ddd / HelpCB.C < prev    next >
C/C++ Source or Header  |  1998-10-03  |  65KB  |  2,569 lines

  1. // $Id: HelpCB.C,v 1.111 1998/10/03 12:09:06 zeller Exp $  -*- C++ -*-
  2. // Interactive Help Callbacks
  3.  
  4. // Copyright (C) 1998 Technische Universitaet Braunschweig, Germany.
  5. // Written by Andreas Zeller <zeller@ips.cs.tu-bs.de>.
  6. // 
  7. // This file is part of DDD.
  8. // 
  9. // DDD is free software; you can redistribute it and/or
  10. // modify it under the terms of the GNU General Public
  11. // License as published by the Free Software Foundation; either
  12. // version 2 of the License, or (at your option) any later version.
  13. // 
  14. // DDD is distributed in the hope that it will be useful,
  15. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  17. // See the GNU General Public License for more details.
  18. // 
  19. // You should have received a copy of the GNU General Public
  20. // License along with DDD -- see the file COPYING.
  21. // If not, write to the Free Software Foundation, Inc.,
  22. // 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  23. // 
  24. // DDD is the data display debugger.
  25. // For details, see the DDD World-Wide-Web page, 
  26. // `http://www.cs.tu-bs.de/softech/ddd/',
  27. // or send a mail to the DDD developers <ddd@ips.cs.tu-bs.de>.
  28.  
  29. char HelpCB_rcsid[] =
  30.     "$Id: HelpCB.C,v 1.111 1998/10/03 12:09:06 zeller Exp $";
  31.  
  32. #include "config.h"
  33.  
  34. #include "Agent.h"
  35. #include "ComboBox.h"
  36. #include "DestroyCB.h"
  37. #include "HelpCB.h"
  38. #include "MakeMenu.h"
  39. #include "SmartC.h"
  40. #include "TimeOut.h"
  41. #include "ddd.h"        // process_pending_events()
  42. #include "findParent.h"
  43. #include "isid.h"
  44. #include "longName.h"
  45. #include "toolbar.h"
  46. #include "windows.h"        // set_scrolled_window_size()
  47. #include "misc.h"        // min(), max()
  48.  
  49. #include <stdio.h>
  50. #include <stdlib.h>
  51. #include <unistd.h>
  52. #include <limits.h>
  53. #include <ctype.h>
  54.  
  55. #include <Xm/Xm.h>
  56. #include <Xm/CascadeB.h>
  57. #include <Xm/MainW.h>
  58. #include <Xm/MessageB.h>
  59. #include <Xm/SelectioB.h>
  60. #include <Xm/RowColumn.h>
  61. #include <Xm/List.h>
  62. #include <Xm/Text.h>
  63. #include <Xm/Label.h>
  64. #include <Xm/Form.h>
  65. #include <Xm/TextF.h>
  66. #include <Xm/PushB.h>
  67. #include <Xm/PanedW.h>
  68. #include <Xm/MenuShell.h>
  69. #include <Xm/ToggleB.h>
  70.  
  71. #include <X11/cursorfont.h>
  72. #include <X11/StringDefs.h>
  73. #include <X11/IntrinsicP.h>    // LessTif hacks
  74. #include <X11/Shell.h>
  75.  
  76. // Misc DDD includes
  77. #include "LessTifH.h"
  78. #include "strclass.h"
  79. #include "cook.h"
  80. #include "events.h"
  81. #include "exit.h"
  82. #include "simpleMenu.h"
  83. #include "verify.h"
  84. #include "Delay.h"
  85. #include "StringA.h"
  86. #include "IntArray.h"
  87. #include "wm.h"
  88. #include "post.h"
  89. #include "mydialogs.h"
  90. #include "ArgField.h"
  91.  
  92.  
  93. //-----------------------------------------------------------------------
  94. // Resources
  95. //-----------------------------------------------------------------------
  96.  
  97. // The help system supports five resources:
  98. // helpString          - displayed in context-sensitive help.
  99. // helpOnVersionString - displayed in `Help On Version'.
  100. // tipString           - displayed in small windows when entering buttons
  101. // documentationString - displayed in the status line
  102. // helpShowTitle       - if set, include widget name in context-sensitive help
  103.  
  104. struct help_resource_values {
  105.     XmString helpString;
  106.     Boolean showTitle;
  107. };
  108.  
  109. struct help_on_version_resource_values {
  110.     XmString helpOnVersionString;
  111.     Boolean showTitle;
  112. };
  113.  
  114. struct tip_resource_values {
  115.     XmString tipString;
  116. };
  117.  
  118. struct doc_resource_values {
  119.     XmString documentationString;
  120. };
  121.  
  122. static XtResource help_subresources[] = {
  123.     {
  124.     XtNhelpString,
  125.     XtCHelpString,
  126.     XmRXmString,
  127.     sizeof(XmString),
  128.     XtOffsetOf(help_resource_values, helpString),
  129.     XtRImmediate,
  130.     XtPointer(0)
  131.     },
  132.  
  133.     {
  134.     XtNhelpShowTitle,
  135.     XtCHelpShowTitle,
  136.     XmRBoolean,
  137.     sizeof(Boolean),
  138.     XtOffsetOf(help_resource_values, showTitle),
  139.     XtRImmediate,
  140.     XtPointer(False)
  141.     }
  142. };
  143.  
  144. static XtResource help_on_version_subresources[] = {
  145.     {
  146.     XtNhelpOnVersionString,
  147.     XtCHelpOnVersionString,
  148.     XmRXmString,
  149.     sizeof(XmString),
  150.     XtOffsetOf(help_on_version_resource_values, helpOnVersionString),
  151.     XtRImmediate,
  152.     XtPointer(0)
  153.     },
  154.  
  155.     {
  156.     XtNhelpShowTitle,
  157.     XtCHelpShowTitle,
  158.     XmRBoolean,
  159.     sizeof(Boolean),
  160.     XtOffsetOf(help_on_version_resource_values, showTitle),
  161.     XtRImmediate,
  162.     XtPointer(False)
  163.     }
  164. };
  165.  
  166. static XtResource tip_subresources[] = {
  167.     {
  168.     XtNtipString,
  169.     XtCTipString,
  170.     XmRXmString,
  171.     sizeof(XmString),
  172.     XtOffsetOf(tip_resource_values, tipString), 
  173.     XtRImmediate,
  174.     XtPointer(0)
  175.     }
  176. };
  177.  
  178. static XtResource doc_subresources[] = {
  179.     {
  180.     XtNdocumentationString,
  181.     XtCDocumentationString,
  182.     XmRXmString,
  183.     sizeof(XmString),
  184.     XtOffsetOf(doc_resource_values, documentationString), 
  185.     XtRImmediate,
  186.     XtPointer(0)
  187.     }
  188. };
  189.  
  190. //-----------------------------------------------------------------------
  191. // Data
  192. //-----------------------------------------------------------------------
  193.  
  194. static Widget help_dialog = 0;
  195. static Widget help_shell  = 0;
  196.  
  197. static MString NoHelpText(Widget widget);
  198. static MString NoTipText(Widget widget, XEvent *event);
  199. static MString NoDocumentationText(Widget widget, XEvent *event);
  200. static void _MStringHelpCB(Widget widget, 
  201.                XtPointer client_data, 
  202.                XtPointer call_data,
  203.                bool help_on_help = false);
  204.  
  205. MString helpOnVersionExtraText;
  206.  
  207.  
  208. //-----------------------------------------------------------------------
  209. // Helpers
  210. //-----------------------------------------------------------------------
  211.  
  212. // A single `\n' means `no string'.  (An empty string causes the
  213. // helpers to be called.)
  214. static bool isNone(const MString& s)
  215. {
  216.     return s.isEmpty() && s.lineCount() > 1;
  217. }
  218.  
  219. static MString get_help_string(Widget widget)
  220. {
  221.     // Get text
  222.     help_resource_values values;
  223.     XtGetApplicationResources(widget, &values, 
  224.                   help_subresources, XtNumber(help_subresources), 
  225.                   NULL, 0);
  226.  
  227.     MString text(values.helpString, true);
  228.     if ((text.isNull() || text.isEmpty()) && DefaultHelpText != 0)
  229.     text = DefaultHelpText(widget);
  230.     if (text.isNull())
  231.     text = NoHelpText(widget);
  232.  
  233.     if (values.showTitle)
  234.     text.prepend(rm("Help for ") + bf(cook(longName(widget)))
  235.              + rm(":") + cr() + cr());
  236.  
  237.     return text;
  238. }
  239.  
  240. static MString get_help_on_version_string(Widget widget)
  241. {
  242.     // Get text
  243.     help_on_version_resource_values values;
  244.     XtGetApplicationResources(widget, &values, 
  245.                   help_on_version_subresources, 
  246.                   XtNumber(help_subresources), 
  247.                   NULL, 0);
  248.  
  249.     MString text(values.helpOnVersionString, true);
  250.     if (text.isNull() || text.isEmpty())
  251.     text = NoHelpText(widget);
  252.  
  253.     if (values.showTitle)
  254.     text.prepend(rm("Help on version for ") + bf(cook(longName(widget)))
  255.              + rm(":") + cr() + cr());
  256.  
  257.     return text;
  258. }
  259.  
  260. static MString _get_tip_string(Widget widget, XEvent *event)
  261. {
  262.     if (XmIsText(widget))
  263.     {
  264.     if (DefaultTipText != 0)
  265.         return DefaultTipText(widget, event);
  266.     return NoTipText(widget, event);
  267.     }
  268.  
  269.     // Get text
  270.     tip_resource_values values;
  271.     XtGetApplicationResources(widget, &values, 
  272.                   tip_subresources, XtNumber(tip_subresources), 
  273.                   NULL, 0);
  274.  
  275.     MString text(values.tipString, true);
  276.     if (text.isNull() || isNone(text))
  277.     return NoTipText(widget, event);
  278.  
  279.     if (text.isEmpty())
  280.     {
  281.     if (DefaultTipText != 0)
  282.         return DefaultTipText(widget, event);
  283.     return NoTipText(widget, event);
  284.     }
  285.  
  286.     return text;
  287. }
  288.  
  289. static MString prepend_label_name(Widget widget, XEvent *event, 
  290.                   MString (*get_string)(Widget, XEvent *))
  291. {
  292.     MString text = get_string(widget, event);
  293.  
  294. #if 0                // Experimental
  295.     if (XtIsSubclass(widget, xmLabelWidgetClass))
  296.     {
  297.     unsigned char label_type = XmSTRING;
  298.     XtVaGetValues(widget, XmNlabelType, &label_type, NULL);
  299.     if (label_type == XmPIXMAP)
  300.     {
  301.         XmString label = 0;
  302.         XtVaGetValues(widget, XmNlabelString, &label, NULL);
  303.         if (label != 0)
  304.         {
  305.         text = MString(label, true) + rm(": ") + text;
  306.         XmStringFree(label);
  307.         }
  308.     }
  309.     }
  310. #endif
  311.  
  312.     return text;
  313. }
  314.  
  315. inline MString get_tip_string(Widget widget, XEvent *event)
  316. {
  317.     return prepend_label_name(widget, event, _get_tip_string);
  318. }
  319.  
  320.  
  321. static MString _get_documentation_string(Widget widget, XEvent *event)
  322. {
  323.     if (XmIsText(widget))
  324.     {
  325.     if (DefaultDocumentationText != 0)
  326.         return DefaultDocumentationText(widget, event);
  327.     return NoDocumentationText(widget, event);
  328.     }
  329.  
  330.     // Get text
  331.     doc_resource_values values;
  332.     XtGetApplicationResources(widget, &values, 
  333.                   doc_subresources, XtNumber(doc_subresources), 
  334.                   NULL, 0);
  335.  
  336.     MString text(values.documentationString, true);
  337.     if (text.isNull())
  338.     return get_tip_string(widget, event);
  339.  
  340.     if (isNone(text))
  341.     return NoDocumentationText(widget, event);
  342.  
  343.     if (text.isEmpty())
  344.     {
  345.     if (DefaultDocumentationText != 0)
  346.         return DefaultDocumentationText(widget, event);
  347.     return NoDocumentationText(widget, event);
  348.     }
  349.  
  350.     return text;
  351. }
  352.  
  353. inline MString get_documentation_string(Widget widget, XEvent *event)
  354. {
  355.     return prepend_label_name(widget, event, _get_documentation_string);
  356. }
  357.  
  358.  
  359. static bool call_tracking_help(XtPointer call_data, bool key_only = false)
  360. {
  361.     XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *)call_data;
  362.  
  363.     if (cbs == 0)
  364.     return key_only;
  365.  
  366.     if (cbs->event == 0)
  367.     return key_only;
  368.  
  369.     if (cbs->event->type != KeyPress && cbs->event->type != KeyRelease)
  370.     return key_only;
  371.  
  372.     if ((cbs->event->xkey.state & ShiftMask) == 0)
  373.     return false;
  374.  
  375.     return true;
  376. }
  377.  
  378. static void nop1(Widget) {}
  379. void (*PostHelpOnItemHook)(Widget) = nop1;
  380.  
  381.  
  382. //-----------------------------------------------------------------------
  383. // Functions
  384. //-----------------------------------------------------------------------
  385.  
  386. void HelpOnHelpCB(Widget widget, XtPointer client_data, XtPointer call_data)
  387. {
  388.     if (help_dialog == 0)
  389.     {
  390.     // Make sure help dialog is created
  391.     ImmediateHelpCB(widget, client_data, call_data);
  392.     }
  393.  
  394.     // Get help on the help dialog
  395.     MString text = get_help_string(help_dialog);
  396.     _MStringHelpCB(widget, XtPointer(text.xmstring()), call_data, true);
  397.  
  398.     PostHelpOnItemHook(help_dialog);
  399. }
  400.  
  401. void ImmediateHelpCB(Widget widget, XtPointer client_data, XtPointer call_data)
  402. {
  403.     if (widget == 0)
  404.     return;
  405.  
  406.     if (call_tracking_help(call_data))
  407.     {
  408.     HelpOnContextCB(widget, client_data, call_data);
  409.     return;
  410.     }
  411.  
  412.     Delay delay;
  413.  
  414.     // Get help on this widget
  415.     MString text = get_help_string(widget);
  416.     MStringHelpCB(widget, XtPointer(text.xmstring()), call_data);
  417.     PostHelpOnItemHook(widget);
  418. }
  419.  
  420. void HelpOnThisCB(Widget widget, XtPointer client_data, XtPointer call_data)
  421. {
  422.     if (widget == 0)
  423.     return;
  424.  
  425.     if (call_tracking_help(call_data))
  426.     {
  427.     HelpOnContextCB(widget, client_data, call_data);
  428.     return;
  429.     }
  430.  
  431.     Delay delay;
  432.  
  433.     Widget w = (Widget)client_data;
  434.     
  435.     // Get help on this widget
  436.     MString text = get_help_string(w);
  437.     MStringHelpCB(widget, XtPointer(text.xmstring()), call_data);
  438.     PostHelpOnItemHook(w);
  439. }
  440.  
  441. void HelpOnWindowCB(Widget widget, XtPointer client_data, XtPointer call_data)
  442. {
  443.     if (call_tracking_help(call_data))
  444.     {
  445.     HelpOnContextCB(widget, client_data, call_data);
  446.     return;
  447.     }
  448.  
  449.     Delay delay;
  450.  
  451.     // Get help on the shell window
  452.     Widget shell = findTopLevelShellParent(widget);
  453.  
  454.     MString text = get_help_string(shell);
  455.     MStringHelpCB(widget, XtPointer(text.xmstring()), call_data);
  456.     PostHelpOnItemHook(shell);
  457. }
  458.  
  459. void HelpOnVersionCB(Widget widget, XtPointer client_data, XtPointer call_data)
  460. {
  461.     if (call_tracking_help(call_data))
  462.     {
  463.     HelpOnContextCB(widget, client_data, call_data);
  464.     return;
  465.     }
  466.  
  467.     Delay delay;
  468.  
  469.     // Get a shell window
  470.     Widget shell = findTopLevelShellParent(widget);
  471.  
  472.     MString text = get_help_on_version_string(shell);
  473.     text += helpOnVersionExtraText;
  474.  
  475.     _MStringHelpCB(widget, XtPointer(text.xmstring()), call_data, false);
  476.  
  477.     // PostHelpOnItemHook(shell);
  478. }
  479.  
  480. static void HelpDestroyCB(Widget, XtPointer client_data, XtPointer)
  481. {
  482.     Widget old_dialog = Widget(client_data);
  483.     if (old_dialog == help_dialog)
  484.     {
  485.     help_dialog = 0;
  486.     help_shell  = 0;
  487.     }
  488. }
  489.  
  490.  
  491. // Default help, tip, and documentation strings
  492. static MString NoHelpText(Widget widget)
  493. {
  494.     MString text = "No help available for \"";
  495.     text += cook(longName(widget));
  496.     text += "\"";
  497.  
  498.     return text;
  499. }
  500.  
  501. static MString NoTipText(Widget, XEvent *)
  502. {
  503.     return MString(0, true);    // Empty string
  504. }
  505.  
  506. static MString NoDocumentationText(Widget, XEvent *)
  507. {
  508.     return MString(0, true);    // Empty string
  509. }
  510.  
  511. static XmTextPosition NoTextPosOfEvent(Widget, XEvent *)
  512. {
  513.     return XmTextPosition(-1);
  514. }
  515.  
  516. MString (*DefaultHelpText)(Widget)                    = NoHelpText;
  517. MString (*DefaultTipText)(Widget, XEvent *)           = NoTipText;
  518. MString (*DefaultDocumentationText)(Widget, XEvent *) = NoDocumentationText;
  519. XmTextPosition (*TextPosOfEvent)(Widget, XEvent *)    = NoTextPosOfEvent;
  520.  
  521.  
  522. void (*DisplayDocumentation)(const MString&) = 0;
  523.  
  524. void StringHelpCB(Widget widget, XtPointer client_data, XtPointer call_data)
  525. {
  526.     MString text = (String)client_data;
  527.  
  528.     MStringHelpCB(widget, text.xmstring(), call_data);
  529. }
  530.  
  531. void MStringHelpCB(Widget widget, XtPointer client_data, XtPointer call_data)
  532. {
  533.     _MStringHelpCB(widget, client_data, call_data);
  534. }
  535.  
  536. static void _MStringHelpCB(Widget widget, 
  537.                XtPointer client_data, 
  538.                XtPointer,
  539.                bool help_on_help)
  540. {
  541.     XmString text = (XmString)client_data;
  542.  
  543.     Arg args[10];
  544.     Cardinal arg = 0;
  545.     XtSetArg(args[arg], XmNmessageString, text); arg++;
  546.  
  547.     Widget shell = findTopLevelShellParent(widget);
  548.     if (shell == 0)
  549.     shell = widget;
  550.  
  551.     if (help_dialog && (shell != help_shell))
  552.     {
  553.     DestroyWhenIdle(help_dialog);
  554.     help_dialog = 0;
  555.     }
  556.  
  557.     help_shell  = shell;
  558.  
  559.     if (help_dialog == 0)
  560.     {
  561.     // Build help_dialog
  562.     XtSetArg(args[arg], XmNdeleteResponse, XmUNMAP); arg++;
  563.  
  564.     help_dialog = 
  565.         verify(XmCreateInformationDialog(shell, "help", args, arg));
  566.     Delay::register_shell(help_dialog);
  567.     XtAddCallback(help_dialog, XmNhelpCallback,
  568.               HelpOnHelpCB, 0);
  569.     XtAddCallback(help_dialog, XtNdestroyCallback, 
  570.               HelpDestroyCB, XtPointer(help_dialog));
  571.  
  572.     XtUnmanageChild(XmMessageBoxGetChild(help_dialog, 
  573.                          XmDIALOG_CANCEL_BUTTON));
  574.     }
  575.     else
  576.     {
  577.     // Setup text for existing dialog
  578.     XtSetValues(help_dialog, args, arg);
  579.     }
  580.  
  581.     // If this is a recursive call, disable the help button
  582.     Widget help_button = 
  583.     XmMessageBoxGetChild(help_dialog, XmDIALOG_HELP_BUTTON);
  584.     set_sensitive(help_button, !help_on_help);
  585.  
  586.     // Popup help_dialog
  587.     manage_and_raise(help_dialog);
  588. }
  589.  
  590.  
  591. static void HelpIndexCB(Widget widget, XtPointer client_data, 
  592.             XtPointer call_data)
  593. {
  594.     Delay delay;
  595.  
  596.     XmListCallbackStruct *cbs = (XmListCallbackStruct *)call_data;
  597.     Widget help_man = Widget(client_data);
  598.     int index = cbs->item_position;
  599.  
  600.     void *userData = 0;
  601.     XtVaGetValues(widget, XmNuserData, &userData, NULL);
  602.     XmTextPosition *positions = (XmTextPosition *)userData;
  603.     if (positions == 0)
  604.     return;            // Not yet set
  605.  
  606.     XmTextPosition pos = positions[index - 1];
  607.  
  608.     XmTextSetInsertionPosition(help_man, pos);
  609.     XmTextSetTopCharacter(help_man, pos);
  610.     XmTextShowPosition(help_man, pos);
  611. }
  612.  
  613.  
  614. // Activate the button given in CLIENT_DATA
  615. static void ActivateCB(Widget, XtPointer client_data, 
  616.                XtPointer call_data)
  617. {
  618.     XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *)call_data;
  619.     
  620.     Widget button = Widget(client_data);
  621.     XtCallActionProc(button, "ArmAndActivate", cbs->event, (String *)0, 0);
  622. }
  623.  
  624. struct FindInfo {
  625.     Widget key;            // The text field holding the search key 
  626.     Widget text;        // The text to be searched
  627. };
  628.  
  629. static bool lock_update_arg = false;
  630.  
  631. static void FindCB(Widget w, XtPointer client_data, XtPointer call_data,
  632.            bool forward)
  633. {
  634.     Delay delay;
  635.  
  636.     XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *)call_data;
  637.  
  638.     FindInfo *fi = (FindInfo *)client_data;
  639.  
  640.     String key_s = XmTextFieldGetString(fi->key);
  641.     string key(key_s);
  642.     XtFree(key_s);
  643.  
  644.     static StringArray find_keys;
  645.  
  646.     int next_occurrence = -1;
  647.     if (key != "")
  648.     {
  649.     find_keys += key;
  650.     smart_sort(find_keys);
  651.     uniq(find_keys);
  652.     ComboBoxSetList(fi->key, find_keys);
  653.  
  654.     String text_s = XmTextGetString(fi->text);
  655.     string text(text_s);
  656.     XtFree(text_s);
  657.  
  658.     // If no uppercase letter is in KEY, make TEXT lowercase
  659.     if (key == downcase(key))
  660.         text.downcase();
  661.  
  662.     XmTextPosition cursor = XmTextGetInsertionPosition(fi->text);
  663.  
  664.     if (forward)
  665.     {
  666.         next_occurrence = text.index(key, cursor);
  667.         if (next_occurrence < 0)
  668.         next_occurrence = text.index(key); // Wrap around
  669.     }
  670.     else
  671.     {
  672.         next_occurrence = text.index(key, cursor - text.length() - 1);
  673.         if (next_occurrence < 0)
  674.         next_occurrence = text.index(key, -1); // Wrap around
  675.     }
  676.     }
  677.  
  678.     if (next_occurrence < 0)
  679.     post_warning(quote(key) + " not found.", "manual_find_error", w);
  680.     else
  681.     {
  682.     // LessTif 0.79 sometimes returns NULL in cbs->event.  Handle this.
  683.     Time tm;
  684.     if (cbs->event != 0)
  685.         tm = time(cbs->event);
  686.     else
  687.         tm = XtLastTimestampProcessed(XtDisplay(fi->text));
  688.  
  689.     lock_update_arg = true;
  690.  
  691.     XmTextSetSelection(fi->text,
  692.                next_occurrence,
  693.                next_occurrence + key.length(),
  694.                tm);
  695.     if (forward)
  696.     {
  697.         XmTextSetInsertionPosition(fi->text, 
  698.                        next_occurrence + key.length());
  699.     }
  700.     else
  701.     {
  702.         XmTextSetInsertionPosition(fi->text, next_occurrence);
  703.     }
  704.  
  705.     lock_update_arg = false;
  706.     }
  707. }
  708.  
  709.  
  710. // Find the next occurrence of the string contained in the widget 
  711. // given in CLIENT_DATA
  712. static void FindForwardCB(Widget w, XtPointer client_data, XtPointer call_data)
  713. {
  714.     FindCB(w, client_data, call_data, true);
  715. }
  716.  
  717. // Find the previous occurrence of the string contained in the widget 
  718. // given in CLIENT_DATA
  719. static void FindBackwardCB(Widget w, XtPointer client_data, 
  720.                XtPointer call_data)
  721. {
  722.     FindCB(w, client_data, call_data, false);
  723. }
  724.  
  725. // Highlight current section after cursor motion
  726. static void HighlightSectionCB(Widget, XtPointer client_data, 
  727.                    XtPointer call_data)
  728. {
  729.     XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)call_data;
  730.     Widget list = Widget(client_data);
  731.  
  732.     XmTextPosition cursor = cbs->newInsert;
  733.  
  734.     void *userData = 0;
  735.     XtVaGetValues(list, XmNuserData, &userData, NULL);
  736.     XmTextPosition *positions = (XmTextPosition *)userData;
  737.     if (positions == 0)
  738.     return;            // Not yet set
  739.  
  740.     int pos = 0;
  741.     while (positions[pos] <= cursor)
  742.     pos++;
  743.  
  744.     ListSetAndSelectPos(list, pos);
  745. }
  746.  
  747. static void SetSelectionCB(Widget w, XtPointer client_data, 
  748.                XtPointer call_data)
  749. {
  750.     if (lock_update_arg)
  751.     return;
  752.  
  753.     ArgField *arg_field = (ArgField *)client_data;
  754.  
  755.     string selection = "";
  756.     String _selection = XmTextGetSelection(w);
  757.     if (_selection != 0)
  758.     {
  759.     selection = _selection;
  760.     XtFree(_selection);
  761.     }
  762.     else
  763.     {
  764.     // No selection - get word at cursor
  765.     XmTextVerifyCallbackStruct *cbs = 
  766.         (XmTextVerifyCallbackStruct *)call_data;
  767.     XmTextPosition cursor = cbs->newInsert;
  768.     String text = XmTextGetString(w);
  769.     XmTextPosition startpos, endpos;
  770.     startpos = endpos = cursor;
  771.  
  772.     while (startpos > 0 && isid(text[startpos - 1]))
  773.         startpos--;
  774.     while (text[endpos] != '\0' && isid(text[endpos]))
  775.         endpos++;
  776.  
  777.     if (endpos > startpos)
  778.         selection = string(text + startpos, endpos - startpos);
  779.     }
  780.  
  781.     if (selection != "")
  782.     {
  783.     selection.downcase();
  784.     while (selection.contains('\n'))
  785.         selection = selection.after('\n');
  786.     arg_field->set_string(selection);
  787.     }
  788. }
  789.  
  790. // Return true iff TEXT contains a manual header line at pos
  791. static bool has_header(char *text, unsigned pos)
  792. {
  793.     if (text[pos] != '\0'
  794.     && !isspace(text[pos])
  795.     && (pos == 0 || text[pos - 1] == '\n')
  796.     && text[pos + 1] != '\b')
  797.     {
  798.     // Non-highlighted character in column 1
  799.     return true;
  800.     }
  801.  
  802.     return false;
  803. }
  804.  
  805. // Note: assumes `man' or `info' format.
  806. void ManualStringHelpCB(Widget widget, XtPointer client_data, 
  807.             XtPointer)
  808. {
  809.     static MString null(0, true);
  810.     string text((char *)client_data);
  811.  
  812.     ManualStringHelpCB(widget, null, text);
  813. }
  814.  
  815.  
  816. // Close action from menu
  817. static void CloseCB(Widget w, XtPointer, XtPointer)
  818. {
  819.     Widget shell = findTopLevelShellParent(w);
  820.     DestroyWhenIdle(shell);
  821. }
  822.  
  823. // Create an empty dialog within a top-level-shell
  824. // FIXME: This should be part of core DDD!
  825. static Widget create_text_dialog(Widget parent, String name, 
  826.                  Arg *args, int arg, Widget& bar)
  827. {
  828.     Widget shell = verify(XtCreateWidget(name, topLevelShellWidgetClass,
  829.                      parent, args, arg));
  830.  
  831.     arg = 0;
  832.     Widget w = XmCreateMainWindow(shell, name, args, arg);
  833.     XtManageChild(w);
  834.  
  835.     MMDesc file_menu[] = 
  836.     {
  837.     { "close", MMPush, { CloseCB, XtPointer(shell) }, 0, 0, 0, 0 },
  838.     { "exit",  MMPush, { DDDExitCB, XtPointer(EXIT_SUCCESS) }, 0, 0, 0, 0},
  839.     MMEnd
  840.     };
  841.  
  842.     MMDesc menubar[] = 
  843.     {
  844.     { "file",     MMMenu, MMNoCB, file_menu, 0, 0, 0 },
  845.     { "edit",     MMMenu, MMNoCB, simple_edit_menu, 0, 0, 0 },
  846.     { "help",     MMMenu | MMHelp, MMNoCB, simple_help_menu, 0, 0, 0 },
  847.     MMEnd
  848.     };
  849.  
  850.     bar = MMcreateMenuBar(w, "menubar", menubar);
  851.  
  852.     // Don't add a callback to `Edit' menu
  853.     menubar[1].items = 0;
  854.  
  855.     MMaddCallbacks(menubar);
  856.     MMaddHelpCallback(menubar, ImmediateHelpCB);
  857.  
  858.     menubar[1].items = simple_edit_menu;
  859.  
  860.     Delay::register_shell(shell);
  861.     InstallButtonTips(shell);
  862.  
  863.     return w;
  864. }
  865.  
  866. static void DeleteFindInfoCB(Widget, XtPointer client_data, XtPointer)
  867. {
  868.     FindInfo *fi = (FindInfo *)client_data;
  869.     delete fi;
  870. }
  871.  
  872. static int max_width(const char *text)
  873. {
  874.     int max_width = 0;
  875.     int width     = 0;
  876.  
  877.     while (*text != '\0')
  878.     {
  879.     switch (*text++)
  880.     {
  881.     case '\b':
  882.         if (width > 0)
  883.         width--;
  884.         break;
  885.  
  886.     case '\n':
  887.         max_width = max(max_width, width);
  888.         width = 0;
  889.         break;
  890.  
  891.     default:
  892.         width++;
  893.     }
  894.     }
  895.  
  896.     return max_width;
  897. }
  898.  
  899. static void ToggleIndexCB(Widget w, XtPointer client_data, XtPointer)
  900. {
  901.     Widget child = Widget(client_data);
  902.  
  903.     if (XmToggleButtonGetState(w))
  904.     manage_paned_child(child);
  905.     else
  906.     XtUnmanageChild(child);
  907. }
  908.  
  909. // Return manual
  910. void ManualStringHelpCB(Widget widget, const MString& title,
  911.             const string& unformatted_text)
  912. {
  913.     // Delay delay;
  914.  
  915.     // Build manual dialog
  916.     Widget toplevel = findTheTopLevelShell(widget);
  917.     if (toplevel == 0)
  918.     return;
  919.  
  920.     // Format text
  921.     string the_text(unformatted_text);
  922.  
  923.     // For efficiency reasons, we access all data in-place via the TEXT ptr.
  924.     char *text = (char *)the_text.chars();
  925.  
  926.     // Set i > 0 if TEXT contains more than one newline
  927.     int i = the_text.index('\n');
  928.     if (i >= 0)
  929.     i = the_text.index('\n', i + 1);
  930.  
  931.     bool manual = !the_text.contains("File:", 0) && i > 0;
  932.     bool info   =  the_text.contains("File:", 0) && i > 0;
  933.  
  934.     int len = the_text.length();
  935.  
  936.     if (manual)
  937.     {
  938.     // Manual page: strip manual headers and footers
  939.     int source = 0;
  940.     int target = 0;
  941.  
  942.     for (;;)
  943.     {
  944.         if (target % 100 == 0)
  945.         process_pending_events();
  946.  
  947.         while (has_header(text, source))
  948.         {
  949.         // Header line: strip line and surrounding blanks.
  950.         // At least using GNU nroff, each header line
  951.         // has three blank lines before and two afterwards.
  952.         if (target > 0)
  953.             target--;
  954.         if (target >= 0 && text[target] == '\n')
  955.             target--;
  956.         if (target >= 0 && text[target] == '\n')
  957.             target--;
  958.         if (target >= 0 && text[target] == '\n')
  959.             target--;
  960.         if (target >= 0 && text[target] == '\n')
  961.             target--;
  962.         target++;
  963.         if (target > 0)
  964.             text[target++] = '\n';
  965.  
  966.         while (text[source] != '\n')
  967.             source++;
  968.         if (text[source] == '\n')
  969.             source++;
  970.         if (text[source] == '\n')
  971.             source++;
  972.         if (text[source] == '\n')
  973.             source++;
  974.         }
  975.         if (text[source] == '\0')
  976.         break;
  977.  
  978.         text[target++] = text[source++];
  979.     }
  980.  
  981.     text[target] = '\0';
  982.     len = target;
  983.     while (target < int(the_text.length()))
  984.         text[target++] = '\0';
  985.     }
  986.     else if (info)
  987.     {
  988.     // Info file: strip menus
  989.     int source = 0;
  990.     int target = 0;
  991.  
  992.     while (text[source] != '\0')
  993.     {
  994.         if (target % 100 == 0)
  995.         process_pending_events();
  996.  
  997.         while (text[source] == '*' && 
  998.            text[source + 1] == ' ' && 
  999.            (source == 0 || text[source - 1] == '\n'))
  1000.         {
  1001.         // Skip menu item
  1002.         while (text[source++] != '\n')
  1003.             ;
  1004.         }
  1005.  
  1006.         text[target++] = text[source++];
  1007.     }
  1008.  
  1009.     text[target] = '\0';
  1010.     len = target;
  1011.     while (target < int(the_text.length()))
  1012.         text[target++] = '\0';
  1013.     }
  1014.  
  1015.     if (info || manual)
  1016.     {
  1017.     // Info and manual: kill multiple empty lines
  1018.     int source = 0;
  1019.     int target = 0;
  1020.  
  1021.     while (text[source] != '\0')
  1022.     {
  1023.         if (target % 100 == 0)
  1024.         process_pending_events();
  1025.  
  1026.         if (text[source] == '\n')
  1027.         {
  1028.         while (text[source] != '\0'
  1029.                && text[source + 1] == '\n'
  1030.                && text[source + 2] == '\n')
  1031.             source++;
  1032.         }
  1033.         text[target++] = text[source++];
  1034.     }
  1035.     text[target] = '\0';
  1036.     len = target;
  1037.     while (target < int(the_text.length()))
  1038.         text[target++] = '\0';
  1039.     }
  1040.  
  1041.     // Find titles
  1042.     StringArray titles;
  1043.     IntArray positions;
  1044.  
  1045.     if (info)
  1046.     {
  1047.     // Info file
  1048.     int source = 0;
  1049.     while (source >= 0)
  1050.     {
  1051.         process_pending_events();
  1052.  
  1053.         assert(the_text.contains("File: ", source));
  1054.  
  1055.         // Fetch title below `File: ' line
  1056.         int start_of_title = the_text.index("\n\n", source) + 2;
  1057.         int end_of_title   = the_text.index("\n", start_of_title);
  1058.         string title = 
  1059.         the_text(start_of_title, end_of_title - start_of_title);
  1060.  
  1061.         // Fetch indent level, using the underline characters
  1062.         int start_of_underline = the_text.index('\n', start_of_title) + 1;
  1063.         static string underlines = "*=-.";
  1064.         int indent = underlines.index(the_text[start_of_underline]);
  1065.         if (indent < 0)
  1066.         indent = underlines.length();
  1067.         if (indent == 0)
  1068.         title.upcase();
  1069.  
  1070.         // Add title and position
  1071.         titles += replicate(' ', indent * 2) + title;
  1072.         positions += source;
  1073.  
  1074.         // Strip `File: ' line
  1075.         the_text.del(source, start_of_title - source);
  1076.         
  1077.         // Find next `File: ' line
  1078.         source = the_text.index("File: ", source);
  1079.     }
  1080.     text = (char *)the_text.chars();
  1081.     len = the_text.length();
  1082.     }
  1083.  
  1084.  
  1085.  
  1086.     Arg args[15];
  1087.     Cardinal arg = 0;
  1088.     Widget menubar;
  1089.     XtSetArg(args[arg], XmNdeleteResponse, XmDESTROY); arg++;
  1090.     Widget text_dialog = create_text_dialog(toplevel, "manual_help", 
  1091.                         args, arg, menubar);
  1092.  
  1093.     arg = 0;
  1094.     XtSetArg(args[arg], XmNmarginWidth,        0); arg++;
  1095.     XtSetArg(args[arg], XmNmarginHeight,       0); arg++;
  1096.     XtSetArg(args[arg], XmNborderWidth,        0); arg++;
  1097.     XtSetArg(args[arg], XmNhighlightThickness, 0); arg++;
  1098.     Widget form = verify(XmCreateForm(text_dialog, "form", args, arg));
  1099.  
  1100.     arg = 0;
  1101.     XtSetArg(args[arg], XmNmarginWidth,      0);                 arg++;
  1102.     XtSetArg(args[arg], XmNmarginHeight,     0);                 arg++;
  1103.     XtSetArg(args[arg], XmNborderWidth,      0);                 arg++;
  1104.     XtSetArg(args[arg], XmNallowResize,      True);              arg++;
  1105.     XtSetArg(args[arg], XmNtopAttachment,    XmATTACH_FORM);     arg++;
  1106.     XtSetArg(args[arg], XmNbottomAttachment, XmATTACH_FORM);     arg++;
  1107.     XtSetArg(args[arg], XmNleftAttachment,   XmATTACH_FORM);     arg++;
  1108.     XtSetArg(args[arg], XmNrightAttachment,  XmATTACH_FORM);     arg++;
  1109.     Widget area = verify(XmCreatePanedWindow(form, "help_area", args, arg));
  1110.     XtManageChild(area);
  1111.  
  1112.     FindInfo *fi = new FindInfo;
  1113.     XtAddCallback(text_dialog, XmNdestroyCallback,
  1114.           DeleteFindInfoCB, XtPointer(fi));
  1115.  
  1116.     MMDesc items [] = 
  1117.     {
  1118.     { "findBackward", MMPush, 
  1119.       { FindBackwardCB, XtPointer(fi) }, 0, 0, 0, 0},
  1120.     { "findForward",  MMPush, 
  1121.       { FindForwardCB, XtPointer(fi) }, 0, 0, 0, 0},
  1122.     MMEnd
  1123.     };
  1124.  
  1125.     Widget arg_label;
  1126.     ArgField *arg_field;
  1127.     Widget toolbar = create_toolbar(area, "toolbar", items, 0, arg_label,
  1128.                     arg_field, XmPIXMAP);
  1129.     fi->key = arg_field->text();
  1130.     XtAddCallback(arg_label, XmNactivateCallback, 
  1131.           ClearTextFieldCB, fi->key);
  1132.     MMaddCallbacks(simple_edit_menu, XtPointer(fi->key));
  1133.  
  1134.     arg = 0;
  1135.     Widget help_index = verify(XmCreateScrolledList(area, "index", args, arg));
  1136.     XtManageChild(help_index);
  1137.     set_scrolled_window_size(help_index);
  1138.  
  1139.     Widget view_index;
  1140.     MMDesc manual_menu[] = 
  1141.     {
  1142.     { "findForward",  MMPush, 
  1143.       { FindForwardCB, XtPointer(fi) }, 0, 0, 0, 0 },
  1144.     { "findBackward", MMPush, 
  1145.       { FindBackwardCB, XtPointer(fi) }, 0, 0, 0, 0 },
  1146.     MMSep,
  1147.     { "viewIndex",    MMToggle, { ToggleIndexCB, 
  1148.                       XtPointer(XtParent(help_index)) }, 
  1149.       NULL, &view_index, 0, 0 },
  1150.     MMEnd
  1151.     };
  1152.  
  1153.     MMDesc more_menubar[] = 
  1154.     {
  1155.     // This is called `source' such that we can re-use the Find
  1156.     // specs from the DDD `Source' meny
  1157.     { "source", MMMenu, MMNoCB, manual_menu, 0, 0, 0 },
  1158.     MMEnd
  1159.     };
  1160.  
  1161.     MMaddItems(menubar, more_menubar);
  1162.     MMaddCallbacks(more_menubar);
  1163.     XtVaSetValues(view_index, XmNset, True, NULL);
  1164.  
  1165.     int columns = max_width(text);
  1166.     columns = min(max(columns, 40), 80) + 1;
  1167.  
  1168.     arg = 0;
  1169.     XtSetArg(args[arg], XmNcolumns,  columns);           arg++;
  1170.     XtSetArg(args[arg], XmNeditable, False);             arg++;
  1171.     XtSetArg(args[arg], XmNeditMode, XmMULTI_LINE_EDIT); arg++;
  1172.     XtSetArg(args[arg], XmNvalue,    "");                arg++;
  1173.     Widget help_man = verify(XmCreateScrolledText(area, "text", args, arg));
  1174.     XtManageChild(help_man);
  1175.     set_scrolled_window_size(help_man);
  1176.     fi->text = help_man;
  1177.  
  1178.     XtWidgetGeometry size;
  1179.     size.request_mode = CWHeight;
  1180.     XtQueryGeometry(toolbar, NULL, &size);
  1181.     XtVaSetValues(toolbar,
  1182.           XmNpaneMaximum, size.height,
  1183.           XmNpaneMinimum, size.height,
  1184.           NULL);
  1185.  
  1186.     XtAddCallback(help_index, XmNsingleSelectionCallback,
  1187.           HelpIndexCB, XtPointer(help_man));
  1188.     XtAddCallback(help_index, XmNmultipleSelectionCallback,
  1189.           HelpIndexCB, XtPointer(help_man));
  1190.     XtAddCallback(help_index, XmNbrowseSelectionCallback,
  1191.           HelpIndexCB, XtPointer(help_man));
  1192.     XtAddCallback(help_index, XmNdefaultActionCallback,
  1193.           HelpIndexCB, XtPointer(help_man));
  1194.     XtAddCallback(text_dialog, XmNhelpCallback,
  1195.           ImmediateHelpCB, XtPointer(help_man));
  1196.  
  1197.     XtAddCallback(help_man, XmNmotionVerifyCallback,
  1198.           HighlightSectionCB, XtPointer(help_index));
  1199.     XtAddCallback(help_man, XmNmotionVerifyCallback,
  1200.           SetSelectionCB, XtPointer(arg_field));
  1201.  
  1202.     XtAddCallback(fi->key, XmNactivateCallback, ActivateCB,
  1203.           XtPointer(items[1].widget));
  1204.  
  1205.     XtVaSetValues(text_dialog, XmNdefaultButton, Widget(0), NULL);
  1206.     
  1207.     XtManageChild(form);
  1208.     InstallButtonTips(text_dialog);
  1209.  
  1210.     // Set title
  1211.     if (!title.isNull())
  1212.     wm_set_name(XtParent(text_dialog), title.str(), title.str());
  1213.  
  1214.     if (manual)
  1215.     {
  1216.     // Manual page: handle underlines
  1217.  
  1218.     bool *underlined    = new bool[len];
  1219.     bool *doublestriked = new bool[len];
  1220.     for (i = 0; i < len; i++)
  1221.         underlined[i] = doublestriked[i] = false;
  1222.  
  1223.     int source = 0;
  1224.     int target = 0;
  1225.  
  1226.     while (text[source] != '\0')
  1227.     {
  1228.         if (target % 100 == 0)
  1229.         process_pending_events();
  1230.  
  1231.         char c = text[target++] = text[source++];
  1232.  
  1233.         if (c == '\b' && target >= 2)
  1234.         {
  1235.         target -= 2;
  1236.         if (text[target] == '_')
  1237.             underlined[target] = true;
  1238.         if (text[target] == text[source])
  1239.             doublestriked[target] = true;
  1240.         }
  1241.     }
  1242.     text[target] = '\0';
  1243.     len = target;
  1244.     while (target < int(the_text.length()))
  1245.         text[target++] = '\0';
  1246.  
  1247.     // Set text
  1248.     XtVaSetValues(help_man, XmNvalue, text, NULL);
  1249.  
  1250.     // Set highlighting
  1251.     XmTextSetHighlight(help_man, 0, XmTextGetLastPosition(help_man), 
  1252.                XmHIGHLIGHT_NORMAL);
  1253.  
  1254.     XmTextPosition underlining    = 0;
  1255.     XmTextPosition doublestriking = 0;
  1256.     for (i = 0; i < len; i++)
  1257.     {
  1258.         if (i % 100 == 0)
  1259.         process_pending_events();
  1260.  
  1261.         if (doublestriked[i] && !doublestriking)
  1262.         {
  1263.         doublestriking = i;
  1264.         }
  1265.         else if (!doublestriked[i] && doublestriking)
  1266.         {
  1267.         // There is no bold face in XmText, normal selection
  1268.         // clutters the screen and secondary selection is already used.
  1269.         // So what shall we do here?  Use the Motif 2.0 CSText?
  1270. #if 0
  1271.         XmTextSetHighlight(help_man, doublestriking, i, 
  1272.                    XmHIGHLIGHT_SELECTED);
  1273.         process_pending_events();
  1274. #endif
  1275.         doublestriking = 0;
  1276.         }
  1277.  
  1278.         if (underlined[i] && !underlining)
  1279.         {
  1280.         underlining = i;
  1281.         }
  1282.         else if (!underlined[i] && underlining)
  1283.         {
  1284.         XmTextSetHighlight(help_man, underlining, i, 
  1285.                    XmHIGHLIGHT_SECONDARY_SELECTED);
  1286.         underlining = 0;
  1287.         }
  1288.     }
  1289.  
  1290.     delete[] underlined;
  1291.     delete[] doublestriked;
  1292.     }
  1293.  
  1294.     if (manual)
  1295.     {
  1296.     // Set titles for manual page
  1297.     int start_of_line = 0;
  1298.  
  1299.     for (int source = 0; source < len; source++)
  1300.     {
  1301.         if (source % 100 == 0)
  1302.         process_pending_events();
  1303.  
  1304.         if (text[source] == '\n')
  1305.         {
  1306.         if (source - start_of_line > 3)
  1307.         {
  1308.             // Check manual title
  1309.             bool is_title = false;
  1310.         
  1311.             if (text[start_of_line] == ' ' &&
  1312.             text[start_of_line + 1] == ' ' &&
  1313.             text[start_of_line + 2] != '\0' &&
  1314.             text[start_of_line + 3] != ' ')
  1315.             is_title = true; // .SS title
  1316.             else if (text[start_of_line] != ' '
  1317.                  && source - start_of_line < 60)
  1318.             is_title = true; // .SH title
  1319.  
  1320.             if (is_title)
  1321.             {
  1322.             titles += 
  1323.                 the_text(start_of_line, source - start_of_line);
  1324.             positions += start_of_line;
  1325.             }
  1326.         }
  1327.  
  1328.         start_of_line = source + 1;
  1329.         }
  1330.     }
  1331.     }
  1332.     else
  1333.     {
  1334.     // Info file, or something else: titles are already set
  1335.  
  1336.     // Set text
  1337.     XtVaSetValues(help_man, XmNvalue, text, NULL);
  1338.  
  1339.     // No highlighting
  1340.     XmTextSetHighlight(help_man, 0, XmTextGetLastPosition(help_man), 
  1341.                XmHIGHLIGHT_NORMAL);
  1342.     }
  1343.  
  1344.     process_pending_events();
  1345.  
  1346.     // Set titles in selection list
  1347.     XmTextPosition *xmpositions = new XmTextPosition[titles.size() + 1];
  1348.     for (i = 0; i < titles.size(); i++)
  1349.     xmpositions[i] = positions[i];
  1350.     xmpositions[i] = INT_MAX;
  1351.  
  1352.     XmStringTable xmtitles = new XmString[titles.size()];
  1353.  
  1354.     for (i = 0; i < titles.size(); i++)
  1355.     {
  1356.     xmtitles[i] = 
  1357.         XmStringCreateLtoR(titles[i], 
  1358.                    titles[i].contains(' ', 0) ? 
  1359.                    CHARSET_RM : CHARSET_BF);
  1360.     }
  1361.  
  1362.     XtVaSetValues(help_index,
  1363.           XmNtopItemPosition,   1,
  1364.           XmNselectedItemCount, 0,
  1365.           XmNitems,             xmtitles,
  1366.           XmNuserData,          xmpositions,
  1367.           XmNitemCount,         titles.size(),
  1368.           NULL);
  1369.  
  1370.     for (i = 0; i < titles.size(); i++)
  1371.     XmStringFree(xmtitles[i]);
  1372.     delete[] xmtitles;
  1373.  
  1374.     process_pending_events();
  1375.  
  1376.     // Enable Text Window
  1377.     Delay::register_shell(XtParent(text_dialog));
  1378.     InstallButtonTips(XtParent(text_dialog));
  1379.     manage_and_raise(text_dialog);
  1380. }
  1381.  
  1382.  
  1383. void TextHelpCB(Widget widget, XtPointer client_data, XtPointer)
  1384. {
  1385.     // Delay delay;
  1386.  
  1387.     string name = "text_help";
  1388.  
  1389.     // If CLIENT_DATA starts with @FOO@, use FOO as dialog name
  1390.     String text = (String)client_data;
  1391.     if (text[0] == '@')
  1392.     {
  1393.     name = text + 1;
  1394.     name = name.before('@');
  1395.     text += name.length() + 2;
  1396.     }
  1397.  
  1398.     // Build help_text
  1399.     Widget toplevel = findTheTopLevelShell(widget);
  1400.     if (toplevel == 0)
  1401.     return;
  1402.  
  1403.     Arg args[15];
  1404.     Cardinal arg = 0;
  1405.     Widget menubar;
  1406.     XtSetArg(args[arg], XmNdeleteResponse, XmDESTROY); arg++;
  1407.     Widget text_dialog = create_text_dialog(toplevel, name, args, arg, 
  1408.                         menubar);
  1409.  
  1410.     arg = 0;
  1411.     Widget form = verify(XmCreateForm(text_dialog, "form", args, arg));
  1412.  
  1413.     arg = 0;
  1414.     XtSetArg(args[arg], XmNmarginWidth,      0);                 arg++;
  1415.     XtSetArg(args[arg], XmNmarginHeight,     0);                 arg++;
  1416.     XtSetArg(args[arg], XmNborderWidth,      0);                 arg++;
  1417.     XtSetArg(args[arg], XmNallowResize,      True);              arg++;
  1418.     XtSetArg(args[arg], XmNtopAttachment,    XmATTACH_FORM);     arg++;
  1419.     XtSetArg(args[arg], XmNbottomAttachment, XmATTACH_FORM);     arg++;
  1420.     XtSetArg(args[arg], XmNleftAttachment,   XmATTACH_FORM);     arg++;
  1421.     XtSetArg(args[arg], XmNrightAttachment,  XmATTACH_FORM);     arg++;
  1422.     Widget area = verify(XmCreatePanedWindow(form, "help_area", args, arg));
  1423.     XtManageChild(area);
  1424.  
  1425.     FindInfo *fi = new FindInfo;
  1426.     XtAddCallback(text_dialog, XmNdestroyCallback,
  1427.           DeleteFindInfoCB, XtPointer(fi));
  1428.  
  1429.     MMDesc items [] = 
  1430.     {
  1431.     { "findBackward", MMPush, 
  1432.       { FindBackwardCB, XtPointer(fi) }, 0, 0, 0, 0 },
  1433.     { "findForward",  MMPush, 
  1434.       { FindForwardCB, XtPointer(fi) }, 0, 0, 0, 0 },
  1435.     MMEnd
  1436.     };
  1437.  
  1438.     Widget arg_label;
  1439.     ArgField *arg_field;
  1440.     Widget toolbar = create_toolbar(area, "toolbar", items, 0, arg_label,
  1441.                     arg_field, XmPIXMAP);
  1442.     fi->key = arg_field->text();
  1443.     XtAddCallback(arg_label, XmNactivateCallback, 
  1444.           ClearTextFieldCB, fi->key);
  1445.     MMaddCallbacks(simple_edit_menu, XtPointer(fi->key));
  1446.  
  1447.     MMDesc manual_menu[] = 
  1448.     {
  1449.     { "findForward",  MMPush, 
  1450.       { FindForwardCB, XtPointer(fi) }, 0, 0, 0, 0},
  1451.     { "findBackward", MMPush,
  1452.       { FindBackwardCB, XtPointer(fi) }, 0, 0, 0, 0},
  1453.     MMEnd
  1454.     };
  1455.  
  1456.     MMDesc more_menubar[] = 
  1457.     {
  1458.     // This is called `source' such that we can re-use the Find
  1459.     // specs from the DDD `Source' meny
  1460.     { "source", MMMenu, MMNoCB, manual_menu, 0, 0, 0 },
  1461.     MMEnd
  1462.     };
  1463.  
  1464.     MMaddItems(menubar, more_menubar);
  1465.     MMaddCallbacks(more_menubar);
  1466.  
  1467.     int columns = max_width(text);
  1468.     columns = min(max(columns, 40), 80) + 1;
  1469.  
  1470.     arg = 0;
  1471.     XtSetArg(args[arg], XmNcolumns,          columns);           arg++;
  1472.     XtSetArg(args[arg], XmNeditable,         False);             arg++;
  1473.     XtSetArg(args[arg], XmNeditMode,         XmMULTI_LINE_EDIT); arg++;
  1474.     XtSetArg(args[arg], XmNvalue,            text); arg++;
  1475.     Widget help_text = verify(XmCreateScrolledText(area, "text", args, arg));
  1476.     XtManageChild(help_text);
  1477.     set_scrolled_window_size(help_text);
  1478.     fi->text = help_text;
  1479.  
  1480.     XtWidgetGeometry size;
  1481.     size.request_mode = CWHeight;
  1482.     XtQueryGeometry(toolbar, NULL, &size);
  1483.     XtVaSetValues(toolbar,
  1484.           XmNpaneMaximum, size.height,
  1485.           XmNpaneMinimum, size.height,
  1486.           NULL);
  1487.  
  1488.     XtAddCallback(text_dialog, XmNhelpCallback, ImmediateHelpCB, XtPointer(0));
  1489.  
  1490.     XtAddCallback(help_text, XmNmotionVerifyCallback,
  1491.           SetSelectionCB, XtPointer(arg_field));
  1492.  
  1493.     XtAddCallback(fi->key, XmNactivateCallback, ActivateCB,
  1494.           XtPointer(items[1].widget));
  1495.  
  1496.     XtManageChild(form);
  1497.     InstallButtonTips(text_dialog);
  1498.  
  1499.     // Enable Text Window
  1500.     XtRealizeWidget(XtParent(text_dialog));
  1501.     Delay::register_shell(XtParent(text_dialog));
  1502.     InstallButtonTips(XtParent(text_dialog));
  1503.     XtPopup(XtParent(text_dialog), XtGrabNone);
  1504.     manage_and_raise(text_dialog);
  1505. }
  1506.  
  1507.  
  1508. //-----------------------------------------------------------------------------
  1509. // Context-sensitive help
  1510. //-----------------------------------------------------------------------------
  1511.  
  1512. // Return the widget related to the mouse event EV
  1513. static Widget EventToWidget(Widget widget, XEvent *ev)
  1514. {
  1515.     // If the button was clicked outside of this programs windows, the
  1516.     // widget that grabbed the pointer will get the event.  So, we
  1517.     // check the bounds of the widget against the coordinates of the
  1518.     // event.  If they're outside, we return 0.  Otherwise we
  1519.     // return the widget in which the event occured.
  1520.  
  1521.     switch (ev->type)
  1522.     {
  1523.     case KeyPress:
  1524.     case KeyRelease:
  1525.     case ButtonPress:
  1526.     case ButtonRelease:
  1527.     break;
  1528.  
  1529.     default:
  1530.     return 0;        // No window
  1531.     }
  1532.  
  1533.     Position x, y;
  1534.     Dimension width, height;
  1535.     XtVaGetValues(widget, XmNx, &x, XmNy, &y,
  1536.           XmNwidth, &width, XmNheight, &height,
  1537.           NULL);
  1538.  
  1539.     if (ev->xbutton.window == XtWindow(widget) &&
  1540.     (ev->xbutton.x < x ||
  1541.      ev->xbutton.y < y ||
  1542.      ev->xbutton.x > x + width ||
  1543.      ev->xbutton.y > y + height))
  1544.     {
  1545.     return 0;
  1546.     }
  1547.     else
  1548.     {
  1549.     return XtWindowToWidget(XtDisplay(widget),
  1550.                 ev->xbutton.window);
  1551.     }
  1552. }
  1553.  
  1554.  
  1555. // In LessTif, XmTrackingEvent() is somewhat broken - it returns on
  1556. // KeyRelease events, and it does not return the event.  Here's an
  1557. // improved implementation.
  1558. static Widget
  1559. TrackingEvent(Widget widget, Cursor cursor,
  1560.           Boolean confine_to, XEvent *event_return)
  1561. {
  1562.     Window confine_to_this;
  1563.     XEvent ev;
  1564.     Boolean key_pressed = False;
  1565.     Time time;
  1566.  
  1567.     if (confine_to)
  1568.     {
  1569.     confine_to_this = XtWindow(widget);
  1570.     }
  1571.     else
  1572.     {
  1573.     confine_to_this = None;
  1574.     }
  1575.  
  1576.     time = XtLastTimestampProcessed(XtDisplay(widget));
  1577.     if (XtGrabPointer(widget, True,
  1578.               ButtonReleaseMask | ButtonPressMask,
  1579.               GrabModeAsync, GrabModeAsync,
  1580.               confine_to_this, cursor, time) != GrabSuccess)
  1581.     {
  1582.     cerr << "TrackingEvent: Could not grab pointer\n";
  1583.     return 0;
  1584.     }
  1585.  
  1586.     while (True)
  1587.     {
  1588.     XtAppNextEvent(XtWidgetToApplicationContext(widget), &ev);
  1589.     time = XtLastTimestampProcessed(XtDisplay(widget));
  1590.  
  1591.     if (ev.type == KeyPress)
  1592.     {
  1593.         // Avoid exiting upon releasing the key that caused
  1594.         // XmTrackingEvent() to be invoked
  1595.         key_pressed = True;
  1596.     }
  1597.     else if ((ev.type == KeyRelease && key_pressed) ||
  1598.          (ev.type == ButtonRelease && ev.xbutton.button == 1))
  1599.     {
  1600.         if (event_return != 0)
  1601.         *event_return = ev;
  1602.  
  1603.         XtUngrabPointer(widget, time);
  1604.  
  1605.         return EventToWidget(widget, &ev);
  1606.     }
  1607.     }
  1608. }
  1609.  
  1610. // Hook before help on context
  1611. static void nop2(Widget, XtPointer, XtPointer) {}
  1612. void (*PreHelpOnContextHook)(Widget w, XtPointer client_data, 
  1613.                  XtPointer call_data) = nop2;
  1614.  
  1615. void HelpOnContextCB(Widget widget, XtPointer client_data, XtPointer call_data)
  1616. {
  1617.     Widget item = 0;
  1618.     Widget toplevel = findTopLevelShellParent(widget);
  1619.  
  1620.     if (toplevel == 0)
  1621.     {
  1622.     HelpOnWindowCB(widget, client_data, call_data);
  1623.     return;
  1624.     }
  1625.        
  1626.     PreHelpOnContextHook(widget, client_data, call_data);
  1627.  
  1628.     static Cursor cursor = 
  1629.     XCreateFontCursor(XtDisplay(toplevel), XC_question_arrow);
  1630.  
  1631.     XEvent ev;
  1632. #if XmVersion < 1002
  1633.     // No XmTrackingEvent() in Motif 1.1
  1634.     item = TrackingEvent(toplevel, cursor, False, &ev);
  1635. #else
  1636.     if (lesstif_version <= 84)
  1637.     item = TrackingEvent(toplevel, cursor, False, &ev);
  1638.     else
  1639.     item = XmTrackingEvent(toplevel, cursor, False, &ev);
  1640. #endif
  1641.  
  1642.     if (item != 0)
  1643.     ImmediateHelpCB(item, client_data, 0);
  1644.     else
  1645.     ImmediateHelpCB(toplevel, client_data, 0);
  1646.  
  1647.     // Some Motif versions get confused if this function is invoked
  1648.     // via a menu accelerator; the keyboard remains grabbed. Hence, we
  1649.     // ungrab it explicitly.
  1650.     XtUngrabKeyboard(toplevel,
  1651.              XtLastTimestampProcessed(XtDisplay(widget)));
  1652. }
  1653.  
  1654.  
  1655. // Return the child widget (or gadget) EX/EY is in, starting with WIDGET.
  1656. static Widget GetWidgetAt(Widget w, int ex, int ey)
  1657. {
  1658.     if (w == 0)
  1659.     return 0;
  1660.  
  1661.     bool once_again = true;
  1662.     while (once_again)
  1663.     {
  1664.     WidgetList children  = 0;
  1665.     Cardinal numChildren = 0;
  1666.  
  1667.     if (XtIsComposite(w))
  1668.     {
  1669.         XtVaGetValues(w, XmNchildren, &children,
  1670.               XmNnumChildren, &numChildren, NULL);
  1671.     }
  1672.  
  1673.     once_again = false;
  1674.  
  1675.     // Look for position within children.
  1676.     // In a form, we may have multiple children overlapping each
  1677.     // other.  Hence, search later children first, as they will be
  1678.     // on top of earlier ones.
  1679.     for (int m = int(numChildren) - 1; m >= 0; m--)
  1680.     {
  1681.         Widget child = children[m];
  1682.         if (XtIsRectObj(child) && XtIsManaged(child))
  1683.         {
  1684.         Dimension cx, cy, cw, ch;
  1685.         XtVaGetValues(child,
  1686.                   XmNx, &cx, XmNy, &cy,
  1687.                   XmNwidth, &cw, XmNheight, &ch,
  1688.                   NULL);
  1689.  
  1690.         if (cx <= ex && ex <= (cx + cw) &&
  1691.             cy <= ey && ey <= (cy + ch))
  1692.         {
  1693.             // Position is within CHILD - restart search
  1694.             once_again = true;
  1695.             w = child;
  1696.             ex -= cx;
  1697.             ey -= cy;
  1698.             break;
  1699.         }
  1700.         }
  1701.     }
  1702.     }
  1703.  
  1704.     return w;
  1705. }
  1706.  
  1707. // Returns the widget the pointer is over, or 0 if it cannot be determined.
  1708. static Widget GetWidgetAt(XKeyEvent *e)
  1709. {
  1710.     return GetWidgetAt(XtWindowToWidget(e->display, e->window), e->x, e->y);
  1711. }
  1712.  
  1713. void HelpOnItemCB(Widget widget, XtPointer client_data, XtPointer call_data)
  1714. {
  1715.     Widget toplevel = findTheTopLevelShell(widget);
  1716.  
  1717.     if (call_tracking_help(call_data, true) || toplevel == 0)
  1718.     {
  1719.     HelpOnContextCB(widget, client_data, call_data);
  1720.     return;
  1721.     }
  1722.  
  1723.     Delay delay;        // Finding the widget may take time
  1724.  
  1725.     XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *)call_data;
  1726.  
  1727.     Widget item = GetWidgetAt(&cbs->event->xkey);
  1728.     if (item == 0)
  1729.     item = toplevel;
  1730.  
  1731.     ImmediateHelpCB(item, client_data, 0);
  1732. }
  1733.  
  1734.  
  1735. //-----------------------------------------------------------------------------
  1736. // Button tips
  1737. //-----------------------------------------------------------------------------
  1738.  
  1739. // True iff button tips are enabled.
  1740. static bool button_tips_enabled       = true;
  1741.  
  1742. // True iff text tips are enabled.
  1743. static bool text_tips_enabled         = true;
  1744.  
  1745. // True iff button docs are enabled.
  1746. static bool button_docs_enabled       = true;
  1747.  
  1748. // True iff text docs are enabled.
  1749. static bool text_docs_enabled         = true;
  1750.  
  1751. // The shell containing the tip label.
  1752. static Widget tip_shell               = 0;
  1753.  
  1754. // The tip label.
  1755. static Widget tip_label               = 0;
  1756.  
  1757. // The tip row; a RowColumn widget surrounding the label.
  1758. static Widget tip_row                 = 0;
  1759.  
  1760. // True if the button tip shell is raised.
  1761. static bool tip_popped_up             = false;
  1762.  
  1763. // The timer used for the delay until the tip is raised.
  1764. static XtIntervalId raise_tip_timer   = 0;
  1765.  
  1766. // The timer used for the delay until the tip is cleared.
  1767. static XtIntervalId clear_tip_timer   = 0;
  1768.  
  1769. // The timer used for the delay until the documentation is shown.
  1770. static XtIntervalId raise_doc_timer   = 0;
  1771.  
  1772. // The timer used for the delay until the documentation is cleared.
  1773. static XtIntervalId clear_doc_timer   = 0;
  1774.  
  1775. // Delay times (in ms)
  1776. int help_button_tip_delay = 750;  // Delay before raising button tip
  1777. int help_value_tip_delay  = 750;  // Delay before raising value tip
  1778. int help_button_doc_delay = 0;    // Delay before showing button doc
  1779. int help_value_doc_delay  = 0;    // Delay before showing value doc
  1780. int help_clear_doc_delay  = 1000; // Delay before clearing doc
  1781. int help_clear_tip_delay  = 50;   // Delay before clearing tip
  1782.  
  1783.  
  1784. // Helper: cancel the timer given in CLIENT_DATA
  1785. static void CancelTimer(Widget, XtPointer client_data, XtPointer)
  1786. {
  1787.     XtIntervalId timer = XtIntervalId(client_data);
  1788.     XtRemoveTimeOut(timer);
  1789. }
  1790.  
  1791. // Helper: cancel RAISE_DOC_TIMER
  1792. static void CancelRaiseDoc(Widget = 0, XtPointer = 0, XtPointer = 0)
  1793. {
  1794.     if (raise_doc_timer)
  1795.     {
  1796.     XtRemoveTimeOut(raise_doc_timer);
  1797.     raise_doc_timer = 0;
  1798.     }
  1799. }
  1800.  
  1801. // Helper: cancel RAISE_TIP_TIMER
  1802. static void CancelRaiseTip(Widget = 0, XtPointer = 0, XtPointer = 0)
  1803. {
  1804.     if (raise_tip_timer)
  1805.     {
  1806.     XtRemoveTimeOut(raise_tip_timer);
  1807.     raise_tip_timer = 0;
  1808.     }
  1809. }
  1810.  
  1811. // Event information passed through timeouts, etc.
  1812. struct TipInfo {
  1813.     XEvent event;        // The event structure
  1814.     Widget widget;        // The widget the event occurred in
  1815. };
  1816.  
  1817. // Raise button tip near the widget given in CLIENT_DATA
  1818. static void PopupTip(XtPointer client_data, XtIntervalId *timer)
  1819. {
  1820.     (void) timer;
  1821.     assert(*timer == raise_tip_timer);
  1822.     raise_tip_timer = 0;
  1823.  
  1824.     TipInfo *ti = (TipInfo *)client_data;
  1825.     Widget& w = ti->widget;
  1826.  
  1827.     if (w == 0)
  1828.     return;
  1829.  
  1830.     XtRemoveCallback(w, XmNdestroyCallback, CancelRaiseTip, 0);
  1831.  
  1832.     MString tip = get_tip_string(w, &ti->event);
  1833.     if (tip.isNull() || isNone(tip) || tip.isEmpty())
  1834.     return;
  1835.  
  1836.     if (!XtIsRealized(w))
  1837.     return;
  1838.  
  1839.     if (tip_shell == 0)
  1840.     {
  1841.     Arg args[10];
  1842.     int arg;
  1843.  
  1844.     arg = 0;
  1845.     XtSetArg(args[arg], XmNallowShellResize, true);             arg++;
  1846.     XtSetArg(args[arg], XmNx, WidthOfScreen(XtScreen(w)) + 1);  arg++;
  1847.     XtSetArg(args[arg], XmNy, HeightOfScreen(XtScreen(w)) + 1); arg++;
  1848.     XtSetArg(args[arg], XmNwidth,            10);               arg++;
  1849.     XtSetArg(args[arg], XmNheight,           10);               arg++;
  1850.     tip_shell = verify(XmCreateMenuShell(findTheTopLevelShell(w),
  1851.                          "tipShell", args, arg));
  1852.  
  1853.     arg = 0;
  1854.     XtSetArg(args[arg], XmNmarginWidth, 0);                     arg++;
  1855.     XtSetArg(args[arg], XmNmarginHeight, 0);                    arg++;
  1856.     XtSetArg(args[arg], XmNresizeWidth, True);                  arg++;
  1857.     XtSetArg(args[arg], XmNresizeHeight, True);                 arg++;
  1858.     XtSetArg(args[arg], XmNborderWidth, 0);                     arg++;
  1859.     XtSetArg(args[arg], XmNshadowThickness, 0);                 arg++;
  1860.     tip_row = verify(XmCreateRowColumn(tip_shell, "tipRow", args, arg));
  1861.     XtManageChild(tip_row);
  1862.  
  1863.     arg = 0;
  1864.     XtSetArg(args[arg], XmNlabelString, tip.xmstring());        arg++;
  1865.     XtSetArg(args[arg], XmNrecomputeSize, true);                arg++;
  1866.     XtSetArg(args[arg], XmNalignment, XmALIGNMENT_BEGINNING);   arg++;
  1867.     tip_label = XmCreateLabel(tip_row, "tipLabel", args, arg);
  1868.     XtManageChild(tip_label);
  1869.  
  1870.     // Simple hack to ensure shell is realized
  1871.     XtPopup(tip_shell, XtGrabNone);
  1872.     XtPopdown(tip_shell);
  1873.     }
  1874.  
  1875.     if (lesstif_version < 1000)
  1876.     {
  1877.     // LessTif fails to resize the shell properly - the border
  1878.     // width is zero.  Use this hack instead.
  1879.     XmFontList font_list;
  1880.     XtVaGetValues(tip_label, XmNfontList, &font_list, NULL);
  1881.     
  1882.     Dimension tip_width  = tip.width(font_list)  + 6;
  1883.     Dimension tip_height = tip.height(font_list) + 6;
  1884.  
  1885.     XtResizeWidget(tip_label, tip_width, tip_height, 0);
  1886.     XtResizeWidget(tip_row,   tip_width, tip_height, 0);
  1887.     XtResizeWidget(tip_shell, tip_width, tip_height, 1);
  1888.     }
  1889.  
  1890.     XtVaSetValues(tip_label, XmNlabelString, tip.xmstring(), NULL);
  1891.  
  1892.     // Find a possible place for the tip.  Consider the alignment of
  1893.     // the parent composite as well as the distance to the screen edge.
  1894.  
  1895.     //            TopLeft TopRight
  1896.     // LeftTop    XXXXXXXXXXXXXXXX RightTop
  1897.     //            XXXXXXXXXXXXXXXX
  1898.     // LeftBottom XXXXXXXXXXXXXXXX RightBottom
  1899.     //         BottomLeft BottomRight
  1900.  
  1901.     enum Placement { LeftTop, RightTop,
  1902.              LeftBottom, RightBottom,
  1903.              BottomLeft, BottomRight,
  1904.              TopLeft, TopRight };
  1905.  
  1906.     static Placement last_placement = BottomRight;
  1907.     static Widget last_parent = 0;
  1908.  
  1909.     Widget parent = XtParent(w);
  1910.     if (parent != last_parent || XmIsText(w))
  1911.     last_placement = BottomRight;
  1912.  
  1913.     // Use 18 runs to find out the best position:
  1914.     // Run 0: try last placement in same alignment
  1915.     // Run 1-8: try at various sides of W.  Don't hide other widgets
  1916.     //          and don't move tip off the screen.
  1917.     // Run 9-17: same as 0-8, but don't care for hiding other widgets.
  1918.     for (int run = 0; run < 18; run++)
  1919.     {
  1920.     Placement placement = last_placement;
  1921.     switch (run % 9)
  1922.     {
  1923.     case 0: placement = last_placement; break;
  1924.     case 1: placement = BottomRight; break;
  1925.     case 2: placement = RightBottom; break;
  1926.     case 3: placement = RightTop;    break;
  1927.     case 4: placement = TopRight;    break;
  1928.     case 5: placement = BottomLeft;  break;
  1929.     case 6: placement = LeftBottom;  break;
  1930.     case 7: placement = LeftTop;     break;
  1931.     case 8: placement = TopLeft;     break;
  1932.     }
  1933.  
  1934.     bool ok = false;
  1935.  
  1936.     if (XmIsRowColumn(parent))
  1937.     {
  1938.         // We're part of a button box: if vertically aligned, place
  1939.         // tip on the right; otherwise, place it at the bottom.
  1940.         unsigned char orientation = XmHORIZONTAL;
  1941.         XtVaGetValues(parent, XmNorientation, &orientation, NULL);
  1942.  
  1943.         switch (placement)
  1944.         {
  1945.         case BottomLeft:
  1946.         case BottomRight:
  1947.         case TopLeft:
  1948.         case TopRight:
  1949.         if (orientation == XmHORIZONTAL)
  1950.             ok = true;
  1951.         break;
  1952.  
  1953.         case LeftBottom:
  1954.         case LeftTop:
  1955.         case RightBottom:
  1956.         case RightTop:
  1957.         if (orientation == XmVERTICAL)
  1958.             ok = true;
  1959.         break;
  1960.         }
  1961.     }
  1962.     else if (XmIsForm(parent))
  1963.     {
  1964.         // We're part of a form: try to place the tip beyond a form
  1965.         // boundary.
  1966.  
  1967.         int fraction_base = 100;
  1968.         XtVaGetValues(parent, XmNfractionBase, &fraction_base, NULL);
  1969.         if (fraction_base == 100)
  1970.         {
  1971.         // Simple form
  1972.         ok = true;
  1973.         }
  1974.         else
  1975.         {
  1976.         // Command tool or likewise
  1977.         unsigned char left_attachment   = XmATTACH_NONE;
  1978.         unsigned char right_attachment  = XmATTACH_NONE;
  1979.         unsigned char top_attachment    = XmATTACH_NONE;
  1980.         unsigned char bottom_attachment = XmATTACH_NONE;
  1981.  
  1982.         XtVaGetValues(w, 
  1983.                   XmNleftAttachment,   &left_attachment,
  1984.                   XmNrightAttachment,  &right_attachment,
  1985.                   XmNtopAttachment,    &top_attachment,
  1986.                   XmNbottomAttachment, &bottom_attachment,
  1987.                   NULL);
  1988.  
  1989.         int left_position   = 0;
  1990.         int right_position  = 0;
  1991.         int top_position    = 0;
  1992.         int bottom_position = 0;
  1993.  
  1994.         XtVaGetValues(w, 
  1995.                   XmNleftPosition,   &left_position,
  1996.                   XmNrightPosition,  &right_position,
  1997.                   XmNtopPosition,    &top_position,
  1998.                   XmNbottomPosition, &bottom_position,
  1999.                   NULL);
  2000.  
  2001.         switch (placement)
  2002.         {
  2003.         case BottomLeft:
  2004.         case BottomRight:
  2005.             if (bottom_attachment == XmATTACH_POSITION
  2006.             && bottom_position >= fraction_base)
  2007.             ok = true;
  2008.             if (bottom_attachment == XmATTACH_FORM
  2009.             || top_attachment == XmATTACH_OPPOSITE_FORM)
  2010.             ok = true;
  2011.             break;
  2012.  
  2013.         case RightBottom:
  2014.         case RightTop:
  2015.             if (right_attachment == XmATTACH_POSITION
  2016.             && right_position >= fraction_base)
  2017.             ok = true;
  2018.             if (right_attachment == XmATTACH_FORM
  2019.             || left_attachment == XmATTACH_OPPOSITE_FORM)
  2020.             ok = true;
  2021.             break;
  2022.  
  2023.         case TopLeft:
  2024.         case TopRight:
  2025.             if (top_attachment == XmATTACH_POSITION
  2026.             && top_position == 0)
  2027.             ok = true;
  2028.             if (top_attachment == XmATTACH_FORM
  2029.             || bottom_attachment == XmATTACH_OPPOSITE_FORM)
  2030.             ok = true;
  2031.             break;
  2032.  
  2033.         case LeftBottom:
  2034.         case LeftTop:
  2035.             if (left_attachment == XmATTACH_POSITION
  2036.             && left_position == 0)
  2037.             ok = true;
  2038.             if (left_attachment == XmATTACH_FORM
  2039.             || right_attachment == XmATTACH_OPPOSITE_FORM)
  2040.             ok = true;
  2041.             break;
  2042.         }
  2043.         }
  2044.     }
  2045.     else
  2046.     {
  2047.         // Any other alignment
  2048.         ok = true;
  2049.     }
  2050.  
  2051.     if (!ok && run <= 8)
  2052.         continue;
  2053.  
  2054.     // Don't move tip off the screen
  2055.     XWindowAttributes w_attributes;
  2056.     XGetWindowAttributes(XtDisplay(w), XtWindow(w), &w_attributes);
  2057.  
  2058.     if (XmIsText(w))
  2059.     {
  2060.         w_attributes.width  = 0;
  2061.         w_attributes.height = 0;
  2062.     }
  2063.  
  2064.     XtWidgetGeometry tip_geometry;
  2065.     tip_geometry.request_mode = CWHeight | CWWidth;
  2066.     XtQueryGeometry(tip_shell, NULL, &tip_geometry);
  2067.  
  2068.     int x_offset = 5;
  2069.     int y_offset = 5;
  2070.  
  2071.     int dx = 0;
  2072.     switch (placement)
  2073.     {
  2074.     case LeftBottom:
  2075.     case LeftTop:
  2076.         dx = -(tip_geometry.width + x_offset);
  2077.         break;
  2078.     case RightBottom:
  2079.     case RightTop:
  2080.         dx = w_attributes.width + x_offset;
  2081.         break;
  2082.     case BottomLeft:
  2083.     case TopLeft:
  2084.         dx = w_attributes.width / 2 - tip_geometry.width;
  2085.         break;
  2086.     case BottomRight:
  2087.     case TopRight:
  2088.         dx = w_attributes.width / 2;
  2089.         break;
  2090.     }
  2091.  
  2092.     int dy = 0;
  2093.     switch (placement)
  2094.     {
  2095.     case LeftBottom:
  2096.     case RightBottom:
  2097.         dy = w_attributes.height / 2;
  2098.         break;
  2099.     case LeftTop:
  2100.     case RightTop:
  2101.         dy = w_attributes.height / 2 - tip_geometry.height;
  2102.         break;
  2103.     case BottomLeft:
  2104.     case BottomRight:
  2105.         dy = w_attributes.height + y_offset;
  2106.         break;
  2107.     case TopLeft:
  2108.     case TopRight:
  2109.         dy = -(tip_geometry.height + y_offset);
  2110.         break;
  2111.     }
  2112.  
  2113.     if (XmIsText(w))
  2114.     {
  2115.         BoxPoint pos = point(&ti->event);
  2116.         dx += pos[X];
  2117.         dy += pos[Y];
  2118.     }
  2119.  
  2120.     // Don't move tip off the screen
  2121.     Window w_child;
  2122.     int x, y;
  2123.     XTranslateCoordinates(XtDisplay(w), XtWindow(w), w_attributes.root,
  2124.                   dx, dy, &x, &y, &w_child);
  2125.     if (x < 0)
  2126.         continue;
  2127.     if (y < 0)
  2128.         continue;
  2129.     if (x + tip_geometry.width >= WidthOfScreen(XtScreen(w)))
  2130.         continue;
  2131.     if (y + tip_geometry.height >= HeightOfScreen(XtScreen(w)))
  2132.         continue;
  2133.  
  2134.     // Move tip to X, Y...
  2135.     XtVaSetValues(tip_shell,
  2136.               XmNx, x,
  2137.               XmNy, y,
  2138.               NULL);
  2139.     
  2140.     // and pop it up.
  2141.     XtPopup(tip_shell, XtGrabNone);
  2142.     tip_popped_up = true;
  2143.     last_placement = placement;
  2144.     last_parent    = parent;
  2145.  
  2146.     return;
  2147.     }
  2148. }
  2149.  
  2150. // Show the documentation for the widget given in CLIENT_DATA
  2151. static void ShowDocumentation(XtPointer client_data, XtIntervalId *timer)
  2152. {
  2153.     (void) timer;
  2154.     assert(*timer == raise_doc_timer);
  2155.     raise_doc_timer = 0;
  2156.  
  2157.     TipInfo *ti = (TipInfo *)client_data;
  2158.     XtRemoveCallback(ti->widget, XmNdestroyCallback, CancelRaiseDoc, 0);
  2159.  
  2160.     if (DisplayDocumentation != 0 
  2161.     && (XmIsText(ti->widget) ? text_docs_enabled : button_docs_enabled))
  2162.     {
  2163.     // Display documentation
  2164.     MString doc = get_documentation_string(ti->widget, &ti->event);
  2165.     DisplayDocumentation(doc);
  2166.     }
  2167. }
  2168.  
  2169. // Clear the documentation
  2170. static void ClearDocumentationNow(Widget w);
  2171.  
  2172. static void CancelClearDocumentation(Widget w, XtPointer, XtPointer)
  2173. {
  2174.     if (clear_doc_timer == 0)
  2175.     return;
  2176.  
  2177.     XtRemoveTimeOut(clear_doc_timer);
  2178.     clear_doc_timer = 0;
  2179.     ClearDocumentationNow(w);
  2180. }
  2181.  
  2182. static void ClearDocumentationNow(Widget w)
  2183. {
  2184.     if (DisplayDocumentation != 0 
  2185.     && (XmIsText(w) ? text_docs_enabled : button_docs_enabled))
  2186.     {
  2187.     // Clear documentation
  2188.     static MString empty(0, true);
  2189.     DisplayDocumentation(empty);
  2190.     }
  2191. }
  2192.  
  2193. static void ClearDocumentation(XtPointer client_data, XtIntervalId *timer)
  2194. {
  2195.     (void) timer;
  2196.     assert(*timer == clear_doc_timer);
  2197.     clear_doc_timer = 0;
  2198.  
  2199.     TipInfo *ti = (TipInfo *)client_data;
  2200.  
  2201.     XtRemoveCallback(ti->widget, XmNdestroyCallback, 
  2202.              CancelClearDocumentation, 0);
  2203.     ClearDocumentationNow(ti->widget);
  2204. }
  2205.  
  2206. // Clear tips and documentation
  2207. static void ClearTip(Widget w, XEvent *event)
  2208. {
  2209.     CancelRaiseTip();
  2210.     CancelRaiseDoc();
  2211.  
  2212.     if (tip_popped_up)
  2213.     {
  2214.     XtPopdown(tip_shell);
  2215.     tip_popped_up = false;
  2216.     }
  2217.  
  2218.     if (clear_doc_timer)
  2219.     {
  2220.     XtRemoveTimeOut(clear_doc_timer);
  2221.     clear_doc_timer = 0;
  2222.     }
  2223.  
  2224.     if (DisplayDocumentation != 0 
  2225.     && (XmIsText(w) ? text_docs_enabled : button_docs_enabled))
  2226.     {
  2227.     // We don't clear the documentation immediately, since the
  2228.     // user might be moving over to another button, and we don't
  2229.     // want flashing documentation strings.
  2230.  
  2231.     static TipInfo ti;
  2232.     ti.event  = *event;
  2233.     ti.widget = w;
  2234.  
  2235.     clear_doc_timer =
  2236.         XtAppAddTimeOut(XtWidgetToApplicationContext(w),
  2237.                 help_clear_doc_delay, 
  2238.                 ClearDocumentation, XtPointer(&ti));
  2239.  
  2240.     // Should the button be destroyed beforehand, cancel timeout
  2241.     XtRemoveCallback(w, XmNdestroyCallback, CancelClearDocumentation, 0);
  2242.     XtAddCallback   (w, XmNdestroyCallback, CancelClearDocumentation, 0);
  2243.     }
  2244. }
  2245.  
  2246. // Raise tips and documentation
  2247. static void RaiseTip(Widget w, XEvent *event)
  2248. {
  2249.     if (DisplayDocumentation != 0
  2250.     && (XmIsText(w) ? text_docs_enabled : button_docs_enabled))
  2251.     {
  2252.     // No need to clear the documentation
  2253.     if (clear_doc_timer)
  2254.     {
  2255.         XtRemoveTimeOut(clear_doc_timer);
  2256.         clear_doc_timer = 0;
  2257.     }
  2258.  
  2259.     static TipInfo ti;
  2260.     ti.event  = *event;
  2261.     ti.widget = w;
  2262.  
  2263.     int doc_delay = 
  2264.         XmIsText(w) ? help_value_doc_delay : help_button_doc_delay;
  2265.  
  2266.     CancelRaiseDoc();
  2267.  
  2268.     raise_doc_timer =
  2269.         XtAppAddTimeOut(XtWidgetToApplicationContext(w),
  2270.                 doc_delay,
  2271.                 ShowDocumentation, XtPointer(&ti));
  2272.  
  2273.     // Should W be destroyed beforehand, cancel timeout
  2274.     XtAddCallback(w, XmNdestroyCallback, CancelRaiseDoc, 0);
  2275.     }
  2276.  
  2277.     if (XmIsText(w) ? text_tips_enabled : button_tips_enabled)
  2278.     {
  2279.     static TipInfo ti;
  2280.     ti.event  = *event;
  2281.     ti.widget = w;
  2282.  
  2283.     int tip_delay = 
  2284.         XmIsText(w) ? help_value_tip_delay : help_button_tip_delay;
  2285.  
  2286.     CancelRaiseTip();
  2287.  
  2288.     raise_tip_timer = 
  2289.         XtAppAddTimeOut(XtWidgetToApplicationContext(w),
  2290.                 tip_delay,
  2291.                 PopupTip, XtPointer(&ti));
  2292.  
  2293.     // Should W be destroyed beforehand, cancel timeout
  2294.     XtAddCallback(w, XmNdestroyCallback, CancelRaiseTip, 0);
  2295.     }
  2296. }
  2297.  
  2298. static void DoClearTip(XtPointer client_data, XtIntervalId *timer)
  2299. {
  2300.     (void) timer;
  2301.     assert(*timer == clear_tip_timer);
  2302.     clear_tip_timer = 0;
  2303.  
  2304.     TipInfo& ti = *((TipInfo *)client_data);
  2305.     ClearTip(ti.widget, &ti.event);
  2306. }
  2307.  
  2308.  
  2309. // Widget W has been entered or left.  Handle event.
  2310. static void HandleTipEvent(Widget w,
  2311.                XtPointer /* client_data */,
  2312.                XEvent *event, 
  2313.                Boolean * /* continue_to_dispatch */)
  2314. {
  2315.     static Widget last_left_widget = 0;
  2316.  
  2317.     switch (event->type)
  2318.     {
  2319.     case EnterNotify:
  2320.     {
  2321.     if (clear_tip_timer != 0)
  2322.     {
  2323.         XtRemoveTimeOut(clear_tip_timer);
  2324.         clear_tip_timer  = 0;
  2325.  
  2326.         if (w != last_left_widget)
  2327.         {
  2328.         // Entered other widget within HELP_CLEAR_TIP_DELAY.
  2329.         ClearTip(w, event);
  2330.         last_left_widget = 0;
  2331.         }
  2332.         else
  2333.         {
  2334.         // Re-entered same widget -- ignore.
  2335.         last_left_widget = 0;
  2336.         break;
  2337.         }
  2338.     }
  2339.  
  2340.     if (!XmIsText(w))
  2341.     {
  2342.         // Clear and re-raise tip
  2343.         ClearTip(w, event);
  2344.         RaiseTip(w, event);
  2345.     }
  2346.     }
  2347.     break;
  2348.  
  2349.     case LeaveNotify:
  2350.     {
  2351.     last_left_widget = w;
  2352.     if (clear_tip_timer != 0)
  2353.     {
  2354.         XtRemoveTimeOut(clear_tip_timer);
  2355.         clear_tip_timer = 0;
  2356.     }
  2357.  
  2358.     // We don't clear the tip immediately, because the DDD ungrab
  2359.     // mechanism may cause the pointer to leave a button and
  2360.     // re-enter it immediately.
  2361.     static TipInfo ti;
  2362.     ti.event  = *event;
  2363.     ti.widget = w;
  2364.  
  2365.     clear_tip_timer = 
  2366.         XtAppAddTimeOut(XtWidgetToApplicationContext(w),
  2367.                 help_clear_tip_delay, 
  2368.                 DoClearTip, &ti);
  2369.     }
  2370.     break;
  2371.  
  2372.     case KeyPress:
  2373.     case KeyRelease:
  2374.     case ButtonPress:
  2375.     case ButtonRelease:
  2376.     ClearTip(w, event);
  2377.     break;
  2378.  
  2379.     case MotionNotify:
  2380.     if (XmIsText(w))
  2381.     {
  2382.         static Widget last_motion_widget           = 0;
  2383.         static XmTextPosition last_motion_position = XmTextPosition(-1);
  2384.         XmTextPosition pos = TextPosOfEvent(w, event);
  2385.  
  2386.         if (w != last_motion_widget || pos != last_motion_position)
  2387.         {
  2388.         last_motion_widget   = w;
  2389.         last_motion_position = pos;
  2390.  
  2391.         ClearTip(w, event);
  2392.         if (pos != XmTextPosition(-1))
  2393.             RaiseTip(w, event);
  2394.         }
  2395.     }
  2396.         break;
  2397.     }
  2398. }
  2399.  
  2400.  
  2401. // (Un)install toolbar tips for W
  2402. static void InstallButtonTipEvents(Widget w, bool install)
  2403. {
  2404.     // If neither `tipString' nor `documentationString' resource is
  2405.     // specified, don't install handler
  2406.     tip_resource_values tip_values;
  2407.     XtGetApplicationResources(w, &tip_values, 
  2408.                   tip_subresources, XtNumber(tip_subresources), 
  2409.                   NULL, 0);
  2410.     doc_resource_values doc_values;
  2411.     XtGetApplicationResources(w, &doc_values, 
  2412.                   doc_subresources, XtNumber(doc_subresources), 
  2413.                   NULL, 0);
  2414.     if (tip_values.tipString == 0 && doc_values.documentationString == 0)
  2415.     return;
  2416.  
  2417.     EventMask event_mask = 
  2418.     EnterWindowMask | LeaveWindowMask | ButtonPress | ButtonRelease 
  2419.     | KeyPress | KeyRelease;
  2420.     if (install)
  2421.     {
  2422.     XtAddEventHandler(w, event_mask, False, 
  2423.               HandleTipEvent, XtPointer(0));
  2424.  
  2425.     }
  2426.     else
  2427.     {
  2428.     XtRemoveEventHandler(w, event_mask, False, 
  2429.                  HandleTipEvent, XtPointer(0));
  2430.     }
  2431. }
  2432.  
  2433.  
  2434. // (Un)install toolbar tips for W and all its descendants
  2435. static void InstallButtonTipsNow(Widget w, bool install)
  2436. {
  2437.     if (w == 0 || !XtIsWidget(w))
  2438.     return;
  2439.  
  2440.     // Install tips for this widget
  2441.     InstallButtonTipEvents(w, install);
  2442.  
  2443.     if (XtIsComposite(w))
  2444.     {
  2445.     // Traverse widget tree
  2446.     WidgetList children   = 0;
  2447.     Cardinal num_children = 0;
  2448.  
  2449.     XtVaGetValues(w,
  2450.               XtNchildren, &children,
  2451.               XtNnumChildren, &num_children,
  2452.               NULL);
  2453.  
  2454.     if (children != 0)
  2455.         for (int i = 0; i < int(num_children); i++)
  2456.         InstallButtonTipsNow(children[i], install);
  2457.     }
  2458.  
  2459.     if (XmIsCascadeButton(w))
  2460.     {
  2461.     // Traverse the menu associated with this button
  2462.     Widget subMenuId = 0;
  2463.     XtVaGetValues(w, XmNsubMenuId, &subMenuId, NULL);
  2464.     if (subMenuId != 0)
  2465.         InstallButtonTipsNow(subMenuId, install);
  2466.     }
  2467.  
  2468.     if (XmIsRowColumn(w))
  2469.     {
  2470.     // Traverse the menu associated with this option menu
  2471.     unsigned char rowColumnType = XmWORK_AREA;
  2472.     Widget subMenuId = 0;
  2473.     XtVaGetValues(w, 
  2474.               XmNsubMenuId, &subMenuId,
  2475.               XmNrowColumnType, &rowColumnType,
  2476.               NULL);
  2477.     if (rowColumnType == XmMENU_OPTION && subMenuId != 0)
  2478.         InstallButtonTipsNow(subMenuId, install);
  2479.     }
  2480. }
  2481.  
  2482. // Callback funtion: install tips now.
  2483. static void InstallButtonTipsTimeOut(XtPointer client_data,
  2484.                      XtIntervalId *timer)
  2485. {
  2486.     Widget w = Widget(client_data);
  2487.     XtRemoveCallback(w, XmNdestroyCallback, 
  2488.              CancelTimer, XtPointer(*timer));
  2489.     InstallButtonTipsNow(w, true);
  2490. }
  2491.  
  2492. // Callback funtion: uninstall tips now.
  2493. static void UnInstallButtonTipsTimeOut(XtPointer client_data, 
  2494.                        XtIntervalId *timer)
  2495. {
  2496.     Widget w = Widget(client_data);
  2497.     XtRemoveCallback(w, XmNdestroyCallback, 
  2498.              CancelTimer, XtPointer(*timer));
  2499.     InstallButtonTipsNow(w, false);
  2500. }
  2501.  
  2502. // (Un)install tips for W and all its descendants.
  2503. // We do this as soon as we are back in the event loop, since we
  2504. // assume all children have been created until then.
  2505. void InstallButtonTips(Widget w, bool install)
  2506. {
  2507.     XtIntervalId timer;
  2508.  
  2509.     if (install)
  2510.     timer = XtAppAddTimeOut(XtWidgetToApplicationContext(w), 0,
  2511.                 InstallButtonTipsTimeOut, XtPointer(w));
  2512.     else
  2513.     timer = XtAppAddTimeOut(XtWidgetToApplicationContext(w), 0,
  2514.                 UnInstallButtonTipsTimeOut, XtPointer(w));
  2515.  
  2516.     // Should W be destroyed beforehand, cancel installation.
  2517.     XtAddCallback(w, XmNdestroyCallback, CancelTimer, XtPointer(timer));
  2518. }
  2519.  
  2520. // Enable or disable button tips
  2521. void EnableButtonTips(bool enable)
  2522. {
  2523.     button_tips_enabled = enable;
  2524. }
  2525.  
  2526. // Enable or disable button docs
  2527. void EnableButtonDocs(bool enable)
  2528. {
  2529.     button_docs_enabled = enable;
  2530. }
  2531.  
  2532.  
  2533.  
  2534. //-----------------------------------------------------------------------------
  2535. // Text tips.
  2536. //-----------------------------------------------------------------------------
  2537.  
  2538. // (Un)install text tips for W.
  2539. void InstallTextTips(Widget w, bool install)
  2540. {
  2541.     EventMask event_mask = EnterWindowMask | LeaveWindowMask 
  2542.     | ButtonPress | ButtonRelease | PointerMotionMask
  2543.     | KeyPress | KeyRelease;
  2544.  
  2545.     if (install)
  2546.     {
  2547.     XtAddEventHandler(w, event_mask, False, 
  2548.               HandleTipEvent, XtPointer(0));
  2549.  
  2550.     }
  2551.     else
  2552.     {
  2553.     XtRemoveEventHandler(w, event_mask, False, 
  2554.                  HandleTipEvent, XtPointer(0));
  2555.     }
  2556. }
  2557.  
  2558. // Enable or disable text tips
  2559. void EnableTextTips(bool enable)
  2560. {
  2561.     text_tips_enabled = enable;
  2562. }
  2563.  
  2564. // Enable or disable text docs
  2565. void EnableTextDocs(bool enable)
  2566. {
  2567.     text_docs_enabled = enable;
  2568. }
  2569.