Propogate colors to new windows on opening (744294)
[nedit.git] / source / calltips.c
blob89f12221a8c8e4c76a30c3aa0bdcfe74409d667f
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"
35 #include "../util/misc.h"
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <limits.h>
42 #include <Xm/Xm.h>
43 #include <Xm/Label.h>
44 #include <X11/Shell.h>
46 #ifdef HAVE_DEBUG_H
47 #include "../debug.h"
48 #endif
50 static char *expandAllTabs( char *text, int tab_width );
53 ** Pop-down a calltip if one exists, else do nothing
55 void KillCalltip(WindowInfo *window, int calltipID) {
56 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
57 TextDKillCalltip( textD, calltipID );
60 void TextDKillCalltip(textDisp *textD, int calltipID) {
61 if( textD->calltip.ID == 0 )
62 return;
63 if( calltipID == 0 || calltipID == textD->calltip.ID ) {
64 XtPopdown( textD->calltipShell );
65 textD->calltip.ID = 0;
70 ** Is a calltip displayed? Returns the calltip ID of the currently displayed
71 ** calltip, or 0 if there is no calltip displayed. If called with
72 ** calltipID != 0, returns 0 unless there is a calltip being
73 ** displayed with that calltipID.
75 int GetCalltipID(WindowInfo *window, int calltipID) {
76 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
77 if( calltipID == 0 )
78 return textD->calltip.ID;
79 else {
80 if( calltipID == textD->calltip.ID)
81 return calltipID;
82 else
83 return 0;
87 #define CALLTIP_EDGE_GUARD 5
88 Boolean offscreenV(XWindowAttributes *screenAttr, int top, int height) {
89 return (top < CALLTIP_EDGE_GUARD ||
90 top + height >= screenAttr->height - CALLTIP_EDGE_GUARD);
94 ** Update the position of the current calltip if one exists, else do nothing
96 void RedrawCalltip(WindowInfo *window, int calltipID) {
97 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
98 TextDRedrawCalltip( textD, calltipID );
100 void TextDRedrawCalltip(textDisp *textD, int calltipID) {
101 int lineHeight = textD->ascent + textD->descent;
102 Position txtX, txtY, borderWidth, abs_x, abs_y, tipWidth, tipHeight;
103 XWindowAttributes screenAttr;
104 int rel_x, rel_y, flip_delta;
106 if( textD->calltip.ID == 0 )
107 return;
108 if( calltipID != 0 && calltipID != textD->calltip.ID )
109 return;
111 /* Get the location/dimensions of the text area */
112 XtVaGetValues(textD->w, XmNx, &txtX, XmNy, &txtY, NULL);
114 if( textD->calltip.anchored ) {
115 /* Put it at the anchor position */
116 if (!TextDPositionToXY(textD, textD->calltip.pos, &rel_x, &rel_y)) {
117 if (textD->calltip.alignMode == TIP_STRICT)
118 TextDKillCalltip(textD, textD->calltip.ID);
119 return;
121 } else {
122 if (textD->calltip.pos < 0) {
123 /* First display of tip with cursor offscreen (detected in
124 ShowCalltip) */
125 textD->calltip.pos = textD->width/2;
126 textD->calltip.hAlign = TIP_CENTER;
127 rel_y = textD->height/3;
128 } else if (!TextDPositionToXY(textD, textD->cursorPos, &rel_x, &rel_y)){
129 /* Window has scrolled and tip is now offscreen */
130 if (textD->calltip.alignMode == TIP_STRICT)
131 TextDKillCalltip(textD, textD->calltip.ID);
132 return;
134 rel_x = textD->calltip.pos;
137 XtVaGetValues(textD->calltipShell, XmNwidth, &tipWidth, XmNheight,
138 &tipHeight, XmNborderWidth, &borderWidth, NULL);
139 rel_x += borderWidth;
140 rel_y += lineHeight/2 + borderWidth;
142 /* Adjust rel_x for horizontal alignment modes */
143 if (textD->calltip.hAlign == TIP_CENTER)
144 rel_x -= tipWidth/2;
145 else if (textD->calltip.hAlign == TIP_RIGHT)
146 rel_x -= tipWidth;
148 /* Adjust rel_y for vertical alignment modes */
149 if (textD->calltip.vAlign == TIP_ABOVE) {
150 flip_delta = tipHeight + lineHeight + 2*borderWidth;
151 rel_y -= flip_delta;
152 } else
153 flip_delta = -(tipHeight + lineHeight + 2*borderWidth);
155 XtTranslateCoords(textD->w, rel_x, rel_y, &abs_x, &abs_y);
157 /* If we're not in strict mode try to keep the tip on-screen */
158 if (textD->calltip.alignMode == TIP_SLOPPY) {
159 XGetWindowAttributes(XtDisplay(textD->w),
160 RootWindowOfScreen(XtScreen(textD->w)), &screenAttr);
162 /* make sure tip doesn't run off right or left side of screen */
163 if (abs_x + tipWidth >= screenAttr.width - CALLTIP_EDGE_GUARD)
164 abs_x = screenAttr.width - tipWidth - CALLTIP_EDGE_GUARD;
165 if (abs_x < CALLTIP_EDGE_GUARD)
166 abs_x = CALLTIP_EDGE_GUARD;
168 /* Try to keep the tip onscreen vertically if possible */
169 if (screenAttr.height > tipHeight &&
170 offscreenV(&screenAttr, abs_y, tipHeight)) {
171 /* Maybe flipping from below to above (or vice-versa) will help */
172 if (!offscreenV(&screenAttr, abs_y + flip_delta, tipHeight))
173 abs_y += flip_delta;
174 /* Make sure the tip doesn't end up *totally* offscreen */
175 else if (abs_y + tipHeight < 0)
176 abs_y = CALLTIP_EDGE_GUARD;
177 else if (abs_y >= screenAttr.height)
178 abs_y = screenAttr.height - tipHeight - CALLTIP_EDGE_GUARD;
179 /* If no case applied, just go with the default placement. */
183 XtVaSetValues( textD->calltipShell, XmNx, abs_x, XmNy, abs_y, NULL );
187 ** Returns a new string with each \t replaced with tab_width spaces or
188 ** a pointer to text if there were no tabs. Returns NULL on malloc failure.
189 ** Note that this is dumb replacement, not smart tab-like behavior! The goal
190 ** is to prevent tabs from turning into squares in calltips, not to get the
191 ** formatting just right.
193 static char *expandAllTabs( char *text, int tab_width ) {
194 int i, len, nTabs=0;
195 char *c, *cCpy, *textCpy;
197 /* First count 'em */
198 for( c = text; *c; ++c )
199 if( *c == '\t' )
200 ++nTabs;
201 if( nTabs == 0 )
202 return text;
204 /* Allocate the new string */
205 len = strlen( text ) + ( tab_width - 1 )*nTabs;
206 textCpy = (char*)malloc( len + 1 );
207 if( !textCpy ) {
208 fprintf(stderr,
209 "nedit: Out of heap memory in expandAllTabs!\n");
210 return NULL;
213 /* Now replace 'em */
214 for( c = text, cCpy = textCpy; *c; ++c, ++cCpy) {
215 if( *c == '\t' ) {
216 for( i = 0; i < tab_width; ++i, ++cCpy )
217 *cCpy = ' ';
218 --cCpy; /* Will be incremented in outer for loop */
219 } else
220 *cCpy = *c;
222 *cCpy = '\0';
223 return textCpy;
227 ** Pop-up a calltip.
228 ** If a calltip is already being displayed it is destroyed and replaced with
229 ** the new calltip. Returns the ID of the calltip or 0 on failure.
231 int ShowCalltip(WindowInfo *window, char *text, Boolean anchored,
232 int pos, int hAlign, int vAlign, int alignMode) {
233 static int StaticCalltipID = 1;
234 textDisp *textD = ((TextWidget)window->lastFocus)->text.textD;
235 int rel_x, rel_y;
236 Position txtX, txtY;
237 char *textCpy;
238 XmString str;
240 /* Destroy any previous calltip */
241 TextDKillCalltip( textD, 0 );
243 /* Make sure the text isn't NULL */
244 if (text == NULL) return 0;
246 /* Expand any tabs in the calltip and make it an XmString */
247 textCpy = expandAllTabs( text, BufGetTabDistance(textD->buffer) );
248 if( textCpy == NULL )
249 return 0; /* Out of memory */
250 str = XmStringCreateLtoR(textCpy, XmFONTLIST_DEFAULT_TAG);
251 if( textCpy != text )
252 free( textCpy );
254 /* Get the location/dimensions of the text area */
255 XtVaGetValues(textD->w,
256 XmNx, &txtX,
257 XmNy, &txtY,
258 NULL);
260 /* Create the calltip widget on first request */
261 if (textD->calltipW == NULL) {
262 Arg args[10];
263 int argcnt = 0;
264 XtSetArg(args[argcnt], XmNsaveUnder, True); argcnt++;
265 XtSetArg(args[argcnt], XmNallowShellResize, True); argcnt++;
267 textD->calltipShell = CreatePopupShellWithBestVis("calltipshell",
268 overrideShellWidgetClass, textD->w, args, argcnt);
270 /* Might want to make this a read-only XmText eventually so that
271 users can copy from it */
272 textD->calltipW = XtVaCreateManagedWidget(
273 "calltip", xmLabelWidgetClass, textD->calltipShell,
274 XmNborderWidth, 1, /* Thin borders */
275 XmNhighlightThickness, 0,
276 XmNalignment, XmALIGNMENT_BEGINNING,
277 XmNforeground, textD->calltipFGPixel,
278 XmNbackground, textD->calltipBGPixel,
279 NULL );
282 /* Set the text on the label */
283 XtVaSetValues( textD->calltipW, XmNlabelString, str, NULL );
284 XmStringFree( str );
286 /* Figure out where to put the tip */
287 if (anchored) {
288 /* Put it at the specified position */
289 /* If position is not displayed, return 0 */
290 if (pos < textD->firstChar || pos > textD->lastChar ) {
291 XBell(TheDisplay, 0);
292 return 0;
294 textD->calltip.pos = pos;
295 } else {
296 /* Put it next to the cursor, or in the center of the window if the
297 cursor is offscreen and mode != strict */
298 if (!TextDPositionToXY(textD, textD->cursorPos, &rel_x, &rel_y)) {
299 if (alignMode == TIP_STRICT) {
300 XBell(TheDisplay, 0);
301 return 0;
303 textD->calltip.pos = -1;
304 } else
305 /* Store the x-offset for use when redrawing */
306 textD->calltip.pos = rel_x;
309 /* Should really bounds-check these enumerations... */
310 textD->calltip.ID = StaticCalltipID;
311 textD->calltip.anchored = anchored;
312 textD->calltip.hAlign = hAlign;
313 textD->calltip.vAlign = vAlign;
314 textD->calltip.alignMode = alignMode;
316 /* Increment the static calltip ID. Macro variables can only be int,
317 not unsigned, so have to work to keep it > 0 on overflow */
318 if(++StaticCalltipID <= 0)
319 StaticCalltipID = 1;
321 /* Realize the calltip's shell so that its width & height are known */
322 XtRealizeWidget( textD->calltipShell );
323 /* Move the calltip and pop it up */
324 TextDRedrawCalltip(textD, 0);
325 XtPopup( textD->calltipShell, XtGrabNone );
326 return textD->calltip.ID;