Minor fix.
[nedit.git] / source / calltips.c
blob89cc7a8f77cbd303313a9de2659f0b167cf624e9
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 RedrawCalltip(WindowInfo *window, int calltipID) {
98 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
99 TextDRedrawCalltip( textD, calltipID );
101 void TextDRedrawCalltip(textDisp *textD, int calltipID) {
102 int lineHeight = textD->ascent + textD->descent;
103 Position txtX, txtY, borderWidth, abs_x, abs_y, tipWidth, tipHeight;
104 XWindowAttributes screenAttr;
105 int rel_x, rel_y, flip_delta;
107 if( textD->calltip.ID == 0 )
108 return;
109 if( calltipID != 0 && calltipID != textD->calltip.ID )
110 return;
112 /* Get the location/dimensions of the text area */
113 XtVaGetValues(textD->w, XmNx, &txtX, XmNy, &txtY, NULL);
115 if( textD->calltip.anchored ) {
116 /* Put it at the anchor position */
117 if (!TextDPositionToXY(textD, textD->calltip.pos, &rel_x, &rel_y)) {
118 if (textD->calltip.alignMode == TIP_STRICT)
119 TextDKillCalltip(textD, textD->calltip.ID);
120 return;
122 } else {
123 if (textD->calltip.pos < 0) {
124 /* First display of tip with cursor offscreen (detected in
125 ShowCalltip) */
126 textD->calltip.pos = textD->width/2;
127 textD->calltip.hAlign = TIP_CENTER;
128 rel_y = textD->height/3;
129 } else if (!TextDPositionToXY(textD, textD->cursorPos, &rel_x, &rel_y)){
130 /* Window has scrolled and tip is now offscreen */
131 if (textD->calltip.alignMode == TIP_STRICT)
132 TextDKillCalltip(textD, textD->calltip.ID);
133 return;
135 rel_x = textD->calltip.pos;
138 XtVaGetValues(textD->calltipShell, XmNwidth, &tipWidth, XmNheight,
139 &tipHeight, XmNborderWidth, &borderWidth, NULL);
140 rel_x += borderWidth;
141 rel_y += lineHeight/2 + borderWidth;
143 /* Adjust rel_x for horizontal alignment modes */
144 if (textD->calltip.hAlign == TIP_CENTER)
145 rel_x -= tipWidth/2;
146 else if (textD->calltip.hAlign == TIP_RIGHT)
147 rel_x -= tipWidth;
149 /* Adjust rel_y for vertical alignment modes */
150 if (textD->calltip.vAlign == TIP_ABOVE) {
151 flip_delta = tipHeight + lineHeight + 2*borderWidth;
152 rel_y -= flip_delta;
153 } else
154 flip_delta = -(tipHeight + lineHeight + 2*borderWidth);
156 XtTranslateCoords(textD->w, rel_x, rel_y, &abs_x, &abs_y);
158 /* If we're not in strict mode try to keep the tip on-screen */
159 if (textD->calltip.alignMode == TIP_SLOPPY) {
160 XGetWindowAttributes(XtDisplay(textD->w),
161 RootWindowOfScreen(XtScreen(textD->w)), &screenAttr);
163 /* make sure tip doesn't run off right or left side of screen */
164 if (abs_x + tipWidth >= screenAttr.width - CALLTIP_EDGE_GUARD)
165 abs_x = screenAttr.width - tipWidth - CALLTIP_EDGE_GUARD;
166 if (abs_x < CALLTIP_EDGE_GUARD)
167 abs_x = CALLTIP_EDGE_GUARD;
169 /* Try to keep the tip onscreen vertically if possible */
170 if (screenAttr.height > tipHeight &&
171 offscreenV(&screenAttr, abs_y, tipHeight)) {
172 /* Maybe flipping from below to above (or vice-versa) will help */
173 if (!offscreenV(&screenAttr, abs_y + flip_delta, tipHeight))
174 abs_y += flip_delta;
175 /* Make sure the tip doesn't end up *totally* offscreen */
176 else if (abs_y + tipHeight < 0)
177 abs_y = CALLTIP_EDGE_GUARD;
178 else if (abs_y >= screenAttr.height)
179 abs_y = screenAttr.height - tipHeight - CALLTIP_EDGE_GUARD;
180 /* If no case applied, just go with the default placement. */
184 XtVaSetValues( textD->calltipShell, XmNx, abs_x, XmNy, abs_y, NULL );
188 ** Returns a new string with each \t replaced with tab_width spaces or
189 ** a pointer to text if there were no tabs. Returns NULL on malloc failure.
190 ** Note that this is dumb replacement, not smart tab-like behavior! The goal
191 ** is to prevent tabs from turning into squares in calltips, not to get the
192 ** formatting just right.
194 static char *expandAllTabs( char *text, int tab_width ) {
195 int i, len, nTabs=0;
196 char *c, *cCpy, *textCpy;
198 /* First count 'em */
199 for( c = text; *c; ++c )
200 if( *c == '\t' )
201 ++nTabs;
202 if( nTabs == 0 )
203 return text;
205 /* Allocate the new string */
206 len = strlen( text ) + ( tab_width - 1 )*nTabs;
207 textCpy = (char*)malloc( len + 1 );
208 if( !textCpy ) {
209 fprintf(stderr,
210 "nedit: Out of heap memory in expandAllTabs!\n");
211 return NULL;
214 /* Now replace 'em */
215 for( c = text, cCpy = textCpy; *c; ++c, ++cCpy) {
216 if( *c == '\t' ) {
217 for( i = 0; i < tab_width; ++i, ++cCpy )
218 *cCpy = ' ';
219 --cCpy; /* Will be incremented in outer for loop */
220 } else
221 *cCpy = *c;
223 *cCpy = '\0';
224 return textCpy;
228 ** Pop-up a calltip.
229 ** If a calltip is already being displayed it is destroyed and replaced with
230 ** the new calltip. Returns the ID of the calltip or 0 on failure.
232 int ShowCalltip(WindowInfo *window, char *text, Boolean anchored,
233 int pos, int hAlign, int vAlign, int alignMode) {
234 static int StaticCalltipID = 1;
235 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
236 int rel_x, rel_y;
237 Position txtX, txtY;
238 char *textCpy;
239 XmString str;
241 /* Destroy any previous calltip */
242 TextDKillCalltip( textD, 0 );
244 /* Make sure the text isn't NULL */
245 if (text == NULL) return 0;
247 /* Expand any tabs in the calltip and make it an XmString */
248 textCpy = expandAllTabs( text, BufGetTabDistance(textD->buffer) );
249 if( textCpy == NULL )
250 return 0; /* Out of memory */
251 str = XmStringCreateLtoR(textCpy, XmFONTLIST_DEFAULT_TAG);
252 if( textCpy != text )
253 free( textCpy );
255 /* Get the location/dimensions of the text area */
256 XtVaGetValues(textD->w,
257 XmNx, &txtX,
258 XmNy, &txtY,
259 NULL);
261 /* Create the calltip widget on first request */
262 if (textD->calltipW == NULL) {
263 Arg args[10];
264 int argcnt = 0;
265 XtSetArg(args[argcnt], XmNsaveUnder, True); argcnt++;
266 XtSetArg(args[argcnt], XmNallowShellResize, True); argcnt++;
268 textD->calltipShell = CreatePopupShellWithBestVis("calltipshell",
269 overrideShellWidgetClass, textD->w, args, argcnt);
271 /* Might want to make this a read-only XmText eventually so that
272 users can copy from it */
273 textD->calltipW = XtVaCreateManagedWidget(
274 "calltip", xmLabelWidgetClass, textD->calltipShell,
275 XmNborderWidth, 1, /* Thin borders */
276 XmNhighlightThickness, 0,
277 XmNalignment, XmALIGNMENT_BEGINNING,
278 XmNforeground, textD->calltipFGPixel,
279 XmNbackground, textD->calltipBGPixel,
280 NULL );
283 /* Set the text on the label */
284 XtVaSetValues( textD->calltipW, XmNlabelString, str, NULL );
285 XmStringFree( str );
287 /* Figure out where to put the tip */
288 if (anchored) {
289 /* Put it at the specified position */
290 /* If position is not displayed, return 0 */
291 if (pos < textD->firstChar || pos > textD->lastChar ) {
292 XBell(TheDisplay, 0);
293 return 0;
295 textD->calltip.pos = pos;
296 } else {
297 /* Put it next to the cursor, or in the center of the window if the
298 cursor is offscreen and mode != strict */
299 if (!TextDPositionToXY(textD, textD->cursorPos, &rel_x, &rel_y)) {
300 if (alignMode == TIP_STRICT) {
301 XBell(TheDisplay, 0);
302 return 0;
304 textD->calltip.pos = -1;
305 } else
306 /* Store the x-offset for use when redrawing */
307 textD->calltip.pos = rel_x;
310 /* Should really bounds-check these enumerations... */
311 textD->calltip.ID = StaticCalltipID;
312 textD->calltip.anchored = anchored;
313 textD->calltip.hAlign = hAlign;
314 textD->calltip.vAlign = vAlign;
315 textD->calltip.alignMode = alignMode;
317 /* Increment the static calltip ID. Macro variables can only be int,
318 not unsigned, so have to work to keep it > 0 on overflow */
319 if(++StaticCalltipID <= 0)
320 StaticCalltipID = 1;
322 /* Realize the calltip's shell so that its width & height are known */
323 XtRealizeWidget( textD->calltipShell );
324 /* Move the calltip and pop it up */
325 TextDRedrawCalltip(textD, 0);
326 XtPopup( textD->calltipShell, XtGrabNone );
327 return textD->calltip.ID;