Fix our use of $em_tab_dist after it was changed to 0 for 'turned of'.
[nedit.git] / source / calltips.c
blob61a9ca102667b1f495d2fb3d05437ac0c4863289
1 /*******************************************************************************
2 * *
3 * calltips.c -- Calltip UI functions (calltip *file* functions are in tags.c) *
4 * *
5 * Copyright (C) 2002 Nathaniel Gray *
6 * *
7 * This is free software; you can redistribute it and/or modify it under the *
8 * terms of the GNU General Public License as published by the Free Software *
9 * Foundation; either version 2 of the License, or (at your option) any later *
10 * version. In addition, you may distribute version of this program linked to *
11 * Motif or Open Motif. See README for details. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * April, 1997 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "text.h"
34 #include "textP.h"
35 #include "calltips.h"
36 #include "../util/misc.h"
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <limits.h>
43 #include <Xm/Xm.h>
44 #include <Xm/Label.h>
45 #include <X11/Shell.h>
47 #ifdef HAVE_DEBUG_H
48 #include "../debug.h"
49 #endif
51 static char *expandAllTabs( char *text, int tab_width );
54 ** Pop-down a calltip if one exists, else do nothing
56 void KillCalltip(WindowInfo *window, int calltipID) {
57 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
58 TextDKillCalltip( textD, calltipID );
61 void TextDKillCalltip(textDisp *textD, int calltipID) {
62 if( textD->calltip.ID == 0 )
63 return;
64 if( calltipID == 0 || calltipID == textD->calltip.ID ) {
65 XtPopdown( textD->calltipShell );
66 textD->calltip.ID = 0;
71 ** Is a calltip displayed? Returns the calltip ID of the currently displayed
72 ** calltip, or 0 if there is no calltip displayed. If called with
73 ** calltipID != 0, returns 0 unless there is a calltip being
74 ** displayed with that calltipID.
76 int GetCalltipID(WindowInfo *window, int calltipID) {
77 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
78 if( calltipID == 0 )
79 return textD->calltip.ID;
80 else {
81 if( calltipID == textD->calltip.ID)
82 return calltipID;
83 else
84 return 0;
88 #define CALLTIP_EDGE_GUARD 5
89 static Boolean offscreenV(XWindowAttributes *screenAttr, int top, int height) {
90 return (top < CALLTIP_EDGE_GUARD ||
91 top + height >= screenAttr->height - CALLTIP_EDGE_GUARD);
95 ** Update the position of the current calltip if one exists, else do nothing
97 void TextDRedrawCalltip(textDisp *textD, int calltipID) {
98 int lineHeight = textD->ascent + textD->descent;
99 Position txtX, txtY, borderWidth, abs_x, abs_y, tipWidth, tipHeight;
100 XWindowAttributes screenAttr;
101 int rel_x, rel_y, flip_delta;
103 if( textD->calltip.ID == 0 )
104 return;
105 if( calltipID != 0 && calltipID != textD->calltip.ID )
106 return;
108 /* Get the location/dimensions of the text area */
109 XtVaGetValues(textD->w, XmNx, &txtX, XmNy, &txtY, NULL);
111 if( textD->calltip.anchored ) {
112 /* Put it at the anchor position */
113 if (!TextDPositionToXY(textD, textD->calltip.pos, &rel_x, &rel_y)) {
114 if (textD->calltip.alignMode == TIP_STRICT)
115 TextDKillCalltip(textD, textD->calltip.ID);
116 return;
118 } else {
119 if (textD->calltip.pos < 0) {
120 /* First display of tip with cursor offscreen (detected in
121 ShowCalltip) */
122 textD->calltip.pos = textD->width/2;
123 textD->calltip.hAlign = TIP_CENTER;
124 rel_y = textD->height/3;
125 } else if (!TextDPositionToXY(textD, textD->cursorPos, &rel_x, &rel_y)){
126 /* Window has scrolled and tip is now offscreen */
127 if (textD->calltip.alignMode == TIP_STRICT)
128 TextDKillCalltip(textD, textD->calltip.ID);
129 return;
131 rel_x = textD->calltip.pos;
134 XtVaGetValues(textD->calltipShell, XmNwidth, &tipWidth, XmNheight,
135 &tipHeight, XmNborderWidth, &borderWidth, NULL);
136 rel_x += borderWidth;
137 rel_y += lineHeight/2 + borderWidth;
139 /* Adjust rel_x for horizontal alignment modes */
140 if (textD->calltip.hAlign == TIP_CENTER)
141 rel_x -= tipWidth/2;
142 else if (textD->calltip.hAlign == TIP_RIGHT)
143 rel_x -= tipWidth;
145 /* Adjust rel_y for vertical alignment modes */
146 if (textD->calltip.vAlign == TIP_ABOVE) {
147 flip_delta = tipHeight + lineHeight + 2*borderWidth;
148 rel_y -= flip_delta;
149 } else
150 flip_delta = -(tipHeight + lineHeight + 2*borderWidth);
152 XtTranslateCoords(textD->w, rel_x, rel_y, &abs_x, &abs_y);
154 /* If we're not in strict mode try to keep the tip on-screen */
155 if (textD->calltip.alignMode == TIP_SLOPPY) {
156 XGetWindowAttributes(XtDisplay(textD->w),
157 RootWindowOfScreen(XtScreen(textD->w)), &screenAttr);
159 /* make sure tip doesn't run off right or left side of screen */
160 if (abs_x + tipWidth >= screenAttr.width - CALLTIP_EDGE_GUARD)
161 abs_x = screenAttr.width - tipWidth - CALLTIP_EDGE_GUARD;
162 if (abs_x < CALLTIP_EDGE_GUARD)
163 abs_x = CALLTIP_EDGE_GUARD;
165 /* Try to keep the tip onscreen vertically if possible */
166 if (screenAttr.height > tipHeight &&
167 offscreenV(&screenAttr, abs_y, tipHeight)) {
168 /* Maybe flipping from below to above (or vice-versa) will help */
169 if (!offscreenV(&screenAttr, abs_y + flip_delta, tipHeight))
170 abs_y += flip_delta;
171 /* Make sure the tip doesn't end up *totally* offscreen */
172 else if (abs_y + tipHeight < 0)
173 abs_y = CALLTIP_EDGE_GUARD;
174 else if (abs_y >= screenAttr.height)
175 abs_y = screenAttr.height - tipHeight - CALLTIP_EDGE_GUARD;
176 /* If no case applied, just go with the default placement. */
180 XtVaSetValues( textD->calltipShell, XmNx, abs_x, XmNy, abs_y, NULL );
184 ** Returns a new string with each \t replaced with tab_width spaces or
185 ** a pointer to text if there were no tabs. Returns NULL on malloc failure.
186 ** Note that this is dumb replacement, not smart tab-like behavior! The goal
187 ** is to prevent tabs from turning into squares in calltips, not to get the
188 ** formatting just right.
190 static char *expandAllTabs( char *text, int tab_width ) {
191 int i, nTabs=0;
192 size_t len;
193 char *c, *cCpy, *textCpy;
195 /* First count 'em */
196 for( c = text; *c; ++c )
197 if( *c == '\t' )
198 ++nTabs;
199 if( nTabs == 0 )
200 return text;
202 /* Allocate the new string */
203 len = strlen( text ) + ( tab_width - 1 )*nTabs;
204 textCpy = (char*)malloc( len + 1 );
205 if( !textCpy ) {
206 fprintf(stderr,
207 "nedit: Out of heap memory in expandAllTabs!\n");
208 return NULL;
211 /* Now replace 'em */
212 for( c = text, cCpy = textCpy; *c; ++c, ++cCpy) {
213 if( *c == '\t' ) {
214 for( i = 0; i < tab_width; ++i, ++cCpy )
215 *cCpy = ' ';
216 --cCpy; /* Will be incremented in outer for loop */
217 } else
218 *cCpy = *c;
220 *cCpy = '\0';
221 return textCpy;
225 ** Pop-up a calltip.
226 ** If a calltip is already being displayed it is destroyed and replaced with
227 ** the new calltip. Returns the ID of the calltip or 0 on failure.
229 int ShowCalltip(WindowInfo *window, char *text, Boolean anchored,
230 int pos, int hAlign, int vAlign, int alignMode) {
231 static int StaticCalltipID = 1;
232 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
233 int rel_x, rel_y;
234 Position txtX, txtY;
235 char *textCpy;
236 XmString str;
238 /* Destroy any previous calltip */
239 TextDKillCalltip( textD, 0 );
241 /* Make sure the text isn't NULL */
242 if (text == NULL) return 0;
244 /* Expand any tabs in the calltip and make it an XmString */
245 textCpy = expandAllTabs( text, BufGetTabDistance(textD->buffer) );
246 if( textCpy == NULL )
247 return 0; /* Out of memory */
248 str = XmStringCreateLtoR(textCpy, XmFONTLIST_DEFAULT_TAG);
249 if( textCpy != text )
250 free( textCpy );
252 /* Get the location/dimensions of the text area */
253 XtVaGetValues(textD->w,
254 XmNx, &txtX,
255 XmNy, &txtY,
256 NULL);
258 /* Create the calltip widget on first request */
259 if (textD->calltipW == NULL) {
260 Arg args[10];
261 int argcnt = 0;
262 XtSetArg(args[argcnt], XmNsaveUnder, True); argcnt++;
263 XtSetArg(args[argcnt], XmNallowShellResize, True); argcnt++;
265 textD->calltipShell = CreatePopupShellWithBestVis("calltipshell",
266 overrideShellWidgetClass, textD->w, args, argcnt);
268 /* Might want to make this a read-only XmText eventually so that
269 users can copy from it */
270 textD->calltipW = XtVaCreateManagedWidget(
271 "calltip", xmLabelWidgetClass, textD->calltipShell,
272 XmNborderWidth, 1, /* Thin borders */
273 XmNhighlightThickness, 0,
274 XmNalignment, XmALIGNMENT_BEGINNING,
275 XmNforeground, textD->calltipFGPixel,
276 XmNbackground, textD->calltipBGPixel,
277 NULL );
280 /* Set the text on the label */
281 XtVaSetValues( textD->calltipW, XmNlabelString, str, NULL );
282 XmStringFree( str );
284 /* Figure out where to put the tip */
285 if (anchored) {
286 /* Put it at the specified position */
287 /* If position is not displayed, return 0 */
288 if (pos < textD->firstChar || pos > textD->lastChar ) {
289 XBell(TheDisplay, 0);
290 return 0;
292 textD->calltip.pos = pos;
293 } else {
294 /* Put it next to the cursor, or in the center of the window if the
295 cursor is offscreen and mode != strict */
296 if (!TextDPositionToXY(textD, textD->cursorPos, &rel_x, &rel_y)) {
297 if (alignMode == TIP_STRICT) {
298 XBell(TheDisplay, 0);
299 return 0;
301 textD->calltip.pos = -1;
302 } else
303 /* Store the x-offset for use when redrawing */
304 textD->calltip.pos = rel_x;
307 /* Should really bounds-check these enumerations... */
308 textD->calltip.ID = StaticCalltipID;
309 textD->calltip.anchored = anchored;
310 textD->calltip.hAlign = hAlign;
311 textD->calltip.vAlign = vAlign;
312 textD->calltip.alignMode = alignMode;
314 /* Increment the static calltip ID. Macro variables can only be int,
315 not unsigned, so have to work to keep it > 0 on overflow */
316 if(++StaticCalltipID <= 0)
317 StaticCalltipID = 1;
319 /* Realize the calltip's shell so that its width & height are known */
320 XtRealizeWidget( textD->calltipShell );
321 /* Move the calltip and pop it up */
322 TextDRedrawCalltip(textD, 0);
323 XtPopup( textD->calltipShell, XtGrabNone );
324 return textD->calltip.ID;