1 /*******************************************************************************
3 * calltips.c -- Calltip UI functions (calltip *file* functions are in tags.c) *
5 * Copyright (C) 2002 Nathaniel Gray *
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 *
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 *
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 *
21 * Nirvana Text Editor *
24 * Written by Mark Edel *
26 *******************************************************************************/
29 #include "../config.h"
35 #include "../util/misc.h"
44 #include <X11/Shell.h>
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 )
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
;
78 return textD
->calltip
.ID
;
80 if( calltipID
== textD
->calltip
.ID
)
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 )
108 if( calltipID
!= 0 && calltipID
!= textD
->calltip
.ID
)
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
);
122 if (textD
->calltip
.pos
< 0) {
123 /* First display of tip with cursor offscreen (detected in
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
);
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
)
145 else if (textD
->calltip
.hAlign
== TIP_RIGHT
)
148 /* Adjust rel_y for vertical alignment modes */
149 if (textD
->calltip
.vAlign
== TIP_ABOVE
) {
150 flip_delta
= tipHeight
+ lineHeight
+ 2*borderWidth
;
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
))
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
) {
195 char *c
, *cCpy
, *textCpy
;
197 /* First count 'em */
198 for( c
= text
; *c
; ++c
)
204 /* Allocate the new string */
205 len
= strlen( text
) + ( tab_width
- 1 )*nTabs
;
206 textCpy
= (char*)malloc( len
+ 1 );
209 "nedit: Out of heap memory in expandAllTabs!\n");
213 /* Now replace 'em */
214 for( c
= text
, cCpy
= textCpy
; *c
; ++c
, ++cCpy
) {
216 for( i
= 0; i
< tab_width
; ++i
, ++cCpy
)
218 --cCpy
; /* Will be incremented in outer for loop */
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
;
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
)
254 /* Get the location/dimensions of the text area */
255 XtVaGetValues(textD
->w
,
260 /* Create the calltip widget on first request */
261 if (textD
->calltipW
== NULL
) {
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
,
282 /* Set the text on the label */
283 XtVaSetValues( textD
->calltipW
, XmNlabelString
, str
, NULL
);
286 /* Figure out where to put the tip */
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);
294 textD
->calltip
.pos
= pos
;
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);
303 textD
->calltip
.pos
= -1;
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)
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
;