- Change help version to 5.4DEV
[nedit.git] / source / calltips.c
blob236432837cc02c6f244d70f4971468d5acf0df66
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. *
11 * *
12 * This software is distributed in the hope that it will be useful, but WITHOUT *
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
15 * for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License along with *
18 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
19 * Place, Suite 330, Boston, MA 02111-1307 USA *
20 * *
21 * Nirvana Text Editor *
22 * April, 1997 *
23 * *
24 * Written by Mark Edel *
25 * *
26 *******************************************************************************/
28 #ifdef HAVE_CONFIG_H
29 #include "../config.h"
30 #endif
32 #include "text.h"
33 #include "textP.h"
34 #include "calltips.h"
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <limits.h>
41 #include <Xm/Xm.h>
42 #include <Xm/Label.h>
43 #include <X11/Shell.h>
45 #ifdef HAVE_DEBUG_H
46 #include "../debug.h"
47 #endif
49 static char *expandAllTabs( char *text, int tab_width );
52 ** Pop-down a calltip if one exists, else do nothing
54 void KillCalltip(WindowInfo *window, int calltipID) {
55 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
56 TextDKillCalltip( textD, calltipID );
59 void TextDKillCalltip(textDisp *textD, int calltipID) {
60 if( textD->calltip.ID == 0 )
61 return;
62 if( calltipID == 0 || calltipID == textD->calltip.ID ) {
63 XtPopdown( textD->calltipShell );
64 textD->calltip.ID = 0;
69 ** Is a calltip displayed? Returns the calltip ID of the currently displayed
70 ** calltip, or 0 if there is no calltip displayed. If called with
71 ** calltipID != 0, returns 0 unless there is a calltip being
72 ** displayed with that calltipID.
74 int GetCalltipID(WindowInfo *window, int calltipID) {
75 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
76 if( calltipID == 0 )
77 return textD->calltip.ID;
78 else {
79 if( calltipID == textD->calltip.ID)
80 return calltipID;
81 else
82 return 0;
86 #define CALLTIP_EDGE_GUARD 5
87 Boolean offscreenV(XWindowAttributes *screenAttr, int top, int height) {
88 return (top < CALLTIP_EDGE_GUARD ||
89 top + height >= screenAttr->height - CALLTIP_EDGE_GUARD);
93 ** Update the position of the current calltip if one exists, else do nothing
95 void RedrawCalltip(WindowInfo *window, int calltipID) {
96 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
97 TextDRedrawCalltip( textD, calltipID );
99 void TextDRedrawCalltip(textDisp *textD, int calltipID) {
100 int lineHeight = textD->ascent + textD->descent;
101 Position txtX, txtY, borderWidth, abs_x, abs_y, tipWidth, tipHeight;
102 XWindowAttributes screenAttr;
103 int rel_x, rel_y, flip_delta;
105 if( textD->calltip.ID == 0 )
106 return;
107 if( calltipID != 0 && calltipID != textD->calltip.ID )
108 return;
110 /* Get the location/dimensions of the text area */
111 XtVaGetValues(textD->w, XmNx, &txtX, XmNy, &txtY, NULL);
113 if( textD->calltip.anchored ) {
114 /* Put it at the anchor position */
115 if (!TextDPositionToXY(textD, textD->calltip.pos, &rel_x, &rel_y)) {
116 if (textD->calltip.alignMode == TIP_STRICT)
117 TextDKillCalltip(textD, textD->calltip.ID);
118 return;
120 } else {
121 if (textD->calltip.pos < 0) {
122 /* First display of tip with cursor offscreen (detected in
123 ShowCalltip) */
124 textD->calltip.pos = textD->width/2;
125 textD->calltip.hAlign = TIP_CENTER;
126 rel_y = textD->height/3;
127 } else if (!TextDPositionToXY(textD, textD->cursorPos, &rel_x, &rel_y)){
128 /* Window has scrolled and tip is now offscreen */
129 if (textD->calltip.alignMode == TIP_STRICT)
130 TextDKillCalltip(textD, textD->calltip.ID);
131 return;
133 rel_x = textD->calltip.pos;
136 XtVaGetValues(textD->calltipShell, XmNwidth, &tipWidth, XmNheight,
137 &tipHeight, XmNborderWidth, &borderWidth, NULL);
138 rel_x += borderWidth;
139 rel_y += lineHeight/2 + borderWidth;
141 /* Adjust rel_x for horizontal alignment modes */
142 if (textD->calltip.hAlign == TIP_CENTER)
143 rel_x -= tipWidth/2;
144 else if (textD->calltip.hAlign == TIP_RIGHT)
145 rel_x -= tipWidth;
147 /* Adjust rel_y for vertical alignment modes */
148 if (textD->calltip.vAlign == TIP_ABOVE) {
149 flip_delta = tipHeight + lineHeight + 2*borderWidth;
150 rel_y -= flip_delta;
151 } else
152 flip_delta = -(tipHeight + lineHeight + 2*borderWidth);
154 XtTranslateCoords(textD->w, rel_x, rel_y, &abs_x, &abs_y);
156 /* If we're not in strict mode try to keep the tip on-screen */
157 if (textD->calltip.alignMode == TIP_SLOPPY) {
158 XGetWindowAttributes(XtDisplay(textD->w),
159 RootWindowOfScreen(XtScreen(textD->w)), &screenAttr);
161 /* make sure tip doesn't run off right or left side of screen */
162 if (abs_x + tipWidth >= screenAttr.width - CALLTIP_EDGE_GUARD)
163 abs_x = screenAttr.width - tipWidth - CALLTIP_EDGE_GUARD;
164 if (abs_x < CALLTIP_EDGE_GUARD)
165 abs_x = CALLTIP_EDGE_GUARD;
167 /* Try to keep the tip onscreen vertically if possible */
168 if (screenAttr.height > tipHeight &&
169 offscreenV(&screenAttr, abs_y, tipHeight)) {
170 /* Maybe flipping from below to above (or vice-versa) will help */
171 if (!offscreenV(&screenAttr, abs_y + flip_delta, tipHeight))
172 abs_y += flip_delta;
173 /* Make sure the tip doesn't end up *totally* offscreen */
174 else if (abs_y + tipHeight < 0)
175 abs_y = CALLTIP_EDGE_GUARD;
176 else if (abs_y >= screenAttr.height)
177 abs_y = screenAttr.height - tipHeight - CALLTIP_EDGE_GUARD;
178 /* If no case applied, just go with the default placement. */
182 XtVaSetValues( textD->calltipShell, XmNx, abs_x, XmNy, abs_y, NULL );
186 ** Returns a new string with each \t replaced with tab_width spaces or
187 ** a pointer to text if there were no tabs. Returns NULL on malloc failure.
188 ** Note that this is dumb replacement, not smart tab-like behavior! The goal
189 ** is to prevent tabs from turning into squares in calltips, not to get the
190 ** formatting just right.
192 static char *expandAllTabs( char *text, int tab_width ) {
193 int i, len, nTabs=0;
194 char *c, *cCpy, *textCpy;
196 /* First count 'em */
197 for( c = text; *c; ++c )
198 if( *c == '\t' )
199 ++nTabs;
200 if( nTabs == 0 )
201 return text;
203 /* Allocate the new string */
204 len = strlen( text ) + ( tab_width - 1 )*nTabs;
205 textCpy = (char*)malloc( len + 1 );
206 if( !textCpy ) {
207 fprintf(stderr,
208 "nedit: Out of heap memory in expandAllTabs!\n");
209 return NULL;
212 /* Now replace 'em */
213 for( c = text, cCpy = textCpy; *c; ++c, ++cCpy) {
214 if( *c == '\t' ) {
215 for( i = 0; i < tab_width; ++i, ++cCpy )
216 *cCpy = ' ';
217 --cCpy; /* Will be incremented in outer for loop */
218 } else
219 *cCpy = *c;
221 *cCpy = '\0';
222 return textCpy;
226 ** Pop-up a calltip.
227 ** If a calltip is already being displayed it is destroyed and replaced with
228 ** the new calltip. Returns the ID of the calltip or 0 on failure.
230 int ShowCalltip(WindowInfo *window, char *text, Boolean anchored,
231 int pos, int hAlign, int vAlign, int alignMode) {
232 static int StaticCalltipID = 1;
233 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
234 int rel_x, rel_y;
235 Position txtX, txtY;
236 char *textCpy;
237 XmString str;
239 /* Destroy any previous calltip */
240 TextDKillCalltip( textD, 0 );
242 /* Make sure the text isn't NULL */
243 if (text == NULL) return 0;
245 /* Expand any tabs in the calltip and make it an XmString */
246 textCpy = expandAllTabs( text, BufGetTabDistance(textD->buffer) );
247 if( textCpy == NULL )
248 return 0; /* Out of memory */
249 str = XmStringCreateLtoR(textCpy, XmFONTLIST_DEFAULT_TAG);
250 if( textCpy != text )
251 free( textCpy );
253 /* Get the location/dimensions of the text area */
254 XtVaGetValues(textD->w,
255 XmNx, &txtX,
256 XmNy, &txtY,
257 NULL);
259 /* Create the calltip widget on first request */
260 if (textD->calltipW == NULL) {
261 textD->calltipShell = XtVaCreatePopupShell(
262 "calltipshell", overrideShellWidgetClass, textD->w,
263 XmNsaveUnder, True,
264 XmNallowShellResize, True,
265 NULL );
267 /* Might want to make this a read-only XmText eventually so that
268 users can copy from it */
269 textD->calltipW = XtVaCreateManagedWidget(
270 "calltip", xmLabelWidgetClass, textD->calltipShell,
271 XmNborderWidth, 1, /* Thin borders */
272 XmNhighlightThickness, 0,
273 XmNalignment, XmALIGNMENT_BEGINNING,
274 NULL );
277 /* Set the text on the label */
278 XtVaSetValues( textD->calltipW, XmNlabelString, str, NULL );
279 XmStringFree( str );
281 /* Figure out where to put the tip */
282 if (anchored) {
283 /* Put it at the specified position */
284 /* If position is not displayed, return 0 */
285 if (pos < textD->firstChar || pos > textD->lastChar ) {
286 XBell(TheDisplay, 0);
287 return 0;
289 textD->calltip.pos = pos;
290 } else {
291 /* Put it next to the cursor, or in the center of the window if the
292 cursor is offscreen and mode != strict */
293 if (!TextDPositionToXY(textD, textD->cursorPos, &rel_x, &rel_y)) {
294 if (alignMode == TIP_STRICT) {
295 XBell(TheDisplay, 0);
296 return 0;
298 textD->calltip.pos = -1;
299 } else
300 /* Store the x-offset for use when redrawing */
301 textD->calltip.pos = rel_x;
304 /* Should really bounds-check these enumerations... */
305 textD->calltip.ID = StaticCalltipID;
306 textD->calltip.anchored = anchored;
307 textD->calltip.hAlign = hAlign;
308 textD->calltip.vAlign = vAlign;
309 textD->calltip.alignMode = alignMode;
311 /* Increment the static calltip ID. Macro variables can only be int,
312 not unsigned, so have to work to keep it > 0 on overflow */
313 if(++StaticCalltipID <= 0)
314 StaticCalltipID = 1;
316 /* Realize the calltip's shell so that its width & height are known */
317 XtRealizeWidget( textD->calltipShell );
318 /* Move the calltip and pop it up */
319 TextDRedrawCalltip(textD, 0);
320 XtPopup( textD->calltipShell, XtGrabNone );
321 return textD->calltip.ID;