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"
43 #include <X11/Shell.h>
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 )
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
;
77 return textD
->calltip
.ID
;
79 if( calltipID
== textD
->calltip
.ID
)
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 )
107 if( calltipID
!= 0 && calltipID
!= textD
->calltip
.ID
)
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
);
121 if (textD
->calltip
.pos
< 0) {
122 /* First display of tip with cursor offscreen (detected in
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
);
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
)
144 else if (textD
->calltip
.hAlign
== TIP_RIGHT
)
147 /* Adjust rel_y for vertical alignment modes */
148 if (textD
->calltip
.vAlign
== TIP_ABOVE
) {
149 flip_delta
= tipHeight
+ lineHeight
+ 2*borderWidth
;
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
))
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
) {
194 char *c
, *cCpy
, *textCpy
;
196 /* First count 'em */
197 for( c
= text
; *c
; ++c
)
203 /* Allocate the new string */
204 len
= strlen( text
) + ( tab_width
- 1 )*nTabs
;
205 textCpy
= (char*)malloc( len
+ 1 );
208 "nedit: Out of heap memory in expandAllTabs!\n");
212 /* Now replace 'em */
213 for( c
= text
, cCpy
= textCpy
; *c
; ++c
, ++cCpy
) {
215 for( i
= 0; i
< tab_width
; ++i
, ++cCpy
)
217 --cCpy
; /* Will be incremented in outer for loop */
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
;
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
)
253 /* Get the location/dimensions of the text area */
254 XtVaGetValues(textD
->w
,
259 /* Create the calltip widget on first request */
260 if (textD
->calltipW
== NULL
) {
261 textD
->calltipShell
= XtVaCreatePopupShell(
262 "calltipshell", overrideShellWidgetClass
, textD
->w
,
264 XmNallowShellResize
, True
,
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
,
277 /* Set the text on the label */
278 XtVaSetValues( textD
->calltipW
, XmNlabelString
, str
, NULL
);
281 /* Figure out where to put the tip */
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);
289 textD
->calltip
.pos
= pos
;
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);
298 textD
->calltip
.pos
= -1;
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)
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
;