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 *
10 * version. In addition, you may distribute version of this program linked to *
11 * Motif or Open Motif. See README for details. *
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 *
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 *
22 * Nirvana Text Editor *
25 * Written by Mark Edel *
27 *******************************************************************************/
30 #include "../config.h"
36 #include "../util/misc.h"
45 #include <X11/Shell.h>
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 )
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
;
79 return textD
->calltip
.ID
;
81 if( calltipID
== textD
->calltip
.ID
)
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 TextDRedrawCalltip(textDisp
*textD
, int calltipID
) {
98 int lineHeight
= textD
->ascent
+ textD
->descent
;
99 Position txtX
, txtY
, borderWidth
, abs_x
, abs_y
, tipWidth
, tipHeight
;
100 XWindowAttributes screenAttr
;
101 int rel_x
, rel_y
, flip_delta
;
103 if( textD
->calltip
.ID
== 0 )
105 if( calltipID
!= 0 && calltipID
!= textD
->calltip
.ID
)
108 /* Get the location/dimensions of the text area */
109 XtVaGetValues(textD
->w
, XmNx
, &txtX
, XmNy
, &txtY
, NULL
);
111 if( textD
->calltip
.anchored
) {
112 /* Put it at the anchor position */
113 if (!TextDPositionToXY(textD
, textD
->calltip
.pos
, &rel_x
, &rel_y
)) {
114 if (textD
->calltip
.alignMode
== TIP_STRICT
)
115 TextDKillCalltip(textD
, textD
->calltip
.ID
);
119 if (textD
->calltip
.pos
< 0) {
120 /* First display of tip with cursor offscreen (detected in
122 textD
->calltip
.pos
= textD
->width
/2;
123 textD
->calltip
.hAlign
= TIP_CENTER
;
124 rel_y
= textD
->height
/3;
125 } else if (!TextDPositionToXY(textD
, textD
->cursorPos
, &rel_x
, &rel_y
)){
126 /* Window has scrolled and tip is now offscreen */
127 if (textD
->calltip
.alignMode
== TIP_STRICT
)
128 TextDKillCalltip(textD
, textD
->calltip
.ID
);
131 rel_x
= textD
->calltip
.pos
;
134 XtVaGetValues(textD
->calltipShell
, XmNwidth
, &tipWidth
, XmNheight
,
135 &tipHeight
, XmNborderWidth
, &borderWidth
, NULL
);
136 rel_x
+= borderWidth
;
137 rel_y
+= lineHeight
/2 + borderWidth
;
139 /* Adjust rel_x for horizontal alignment modes */
140 if (textD
->calltip
.hAlign
== TIP_CENTER
)
142 else if (textD
->calltip
.hAlign
== TIP_RIGHT
)
145 /* Adjust rel_y for vertical alignment modes */
146 if (textD
->calltip
.vAlign
== TIP_ABOVE
) {
147 flip_delta
= tipHeight
+ lineHeight
+ 2*borderWidth
;
150 flip_delta
= -(tipHeight
+ lineHeight
+ 2*borderWidth
);
152 XtTranslateCoords(textD
->w
, rel_x
, rel_y
, &abs_x
, &abs_y
);
154 /* If we're not in strict mode try to keep the tip on-screen */
155 if (textD
->calltip
.alignMode
== TIP_SLOPPY
) {
156 XGetWindowAttributes(XtDisplay(textD
->w
),
157 RootWindowOfScreen(XtScreen(textD
->w
)), &screenAttr
);
159 /* make sure tip doesn't run off right or left side of screen */
160 if (abs_x
+ tipWidth
>= screenAttr
.width
- CALLTIP_EDGE_GUARD
)
161 abs_x
= screenAttr
.width
- tipWidth
- CALLTIP_EDGE_GUARD
;
162 if (abs_x
< CALLTIP_EDGE_GUARD
)
163 abs_x
= CALLTIP_EDGE_GUARD
;
165 /* Try to keep the tip onscreen vertically if possible */
166 if (screenAttr
.height
> tipHeight
&&
167 offscreenV(&screenAttr
, abs_y
, tipHeight
)) {
168 /* Maybe flipping from below to above (or vice-versa) will help */
169 if (!offscreenV(&screenAttr
, abs_y
+ flip_delta
, tipHeight
))
171 /* Make sure the tip doesn't end up *totally* offscreen */
172 else if (abs_y
+ tipHeight
< 0)
173 abs_y
= CALLTIP_EDGE_GUARD
;
174 else if (abs_y
>= screenAttr
.height
)
175 abs_y
= screenAttr
.height
- tipHeight
- CALLTIP_EDGE_GUARD
;
176 /* If no case applied, just go with the default placement. */
180 XtVaSetValues( textD
->calltipShell
, XmNx
, abs_x
, XmNy
, abs_y
, NULL
);
184 ** Returns a new string with each \t replaced with tab_width spaces or
185 ** a pointer to text if there were no tabs. Returns NULL on malloc failure.
186 ** Note that this is dumb replacement, not smart tab-like behavior! The goal
187 ** is to prevent tabs from turning into squares in calltips, not to get the
188 ** formatting just right.
190 static char *expandAllTabs( char *text
, int tab_width
) {
193 char *c
, *cCpy
, *textCpy
;
195 /* First count 'em */
196 for( c
= text
; *c
; ++c
)
202 /* Allocate the new string */
203 len
= strlen( text
) + ( tab_width
- 1 )*nTabs
;
204 textCpy
= (char*)malloc( len
+ 1 );
207 "nedit: Out of heap memory in expandAllTabs!\n");
211 /* Now replace 'em */
212 for( c
= text
, cCpy
= textCpy
; *c
; ++c
, ++cCpy
) {
214 for( i
= 0; i
< tab_width
; ++i
, ++cCpy
)
216 --cCpy
; /* Will be incremented in outer for loop */
226 ** If a calltip is already being displayed it is destroyed and replaced with
227 ** the new calltip. Returns the ID of the calltip or 0 on failure.
229 int ShowCalltip(WindowInfo
*window
, char *text
, Boolean anchored
,
230 int pos
, int hAlign
, int vAlign
, int alignMode
) {
231 static int StaticCalltipID
= 1;
232 textDisp
*textD
= ((TextWidget
)window
->lastFocus
)->text
.textD
;
238 /* Destroy any previous calltip */
239 TextDKillCalltip( textD
, 0 );
241 /* Make sure the text isn't NULL */
242 if (text
== NULL
) return 0;
244 /* Expand any tabs in the calltip and make it an XmString */
245 textCpy
= expandAllTabs( text
, BufGetTabDistance(textD
->buffer
) );
246 if( textCpy
== NULL
)
247 return 0; /* Out of memory */
248 str
= XmStringCreateLtoR(textCpy
, XmFONTLIST_DEFAULT_TAG
);
249 if( textCpy
!= text
)
252 /* Get the location/dimensions of the text area */
253 XtVaGetValues(textD
->w
,
258 /* Create the calltip widget on first request */
259 if (textD
->calltipW
== NULL
) {
262 XtSetArg(args
[argcnt
], XmNsaveUnder
, True
); argcnt
++;
263 XtSetArg(args
[argcnt
], XmNallowShellResize
, True
); argcnt
++;
265 textD
->calltipShell
= CreatePopupShellWithBestVis("calltipshell",
266 overrideShellWidgetClass
, textD
->w
, args
, argcnt
);
268 /* Might want to make this a read-only XmText eventually so that
269 users can copy from it */
270 textD
->calltipW
= XtVaCreateManagedWidget(
271 "calltip", xmLabelWidgetClass
, textD
->calltipShell
,
272 XmNborderWidth
, 1, /* Thin borders */
273 XmNhighlightThickness
, 0,
274 XmNalignment
, XmALIGNMENT_BEGINNING
,
275 XmNforeground
, textD
->calltipFGPixel
,
276 XmNbackground
, textD
->calltipBGPixel
,
280 /* Set the text on the label */
281 XtVaSetValues( textD
->calltipW
, XmNlabelString
, str
, NULL
);
284 /* Figure out where to put the tip */
286 /* Put it at the specified position */
287 /* If position is not displayed, return 0 */
288 if (pos
< textD
->firstChar
|| pos
> textD
->lastChar
) {
289 XBell(TheDisplay
, 0);
292 textD
->calltip
.pos
= pos
;
294 /* Put it next to the cursor, or in the center of the window if the
295 cursor is offscreen and mode != strict */
296 if (!TextDPositionToXY(textD
, textD
->cursorPos
, &rel_x
, &rel_y
)) {
297 if (alignMode
== TIP_STRICT
) {
298 XBell(TheDisplay
, 0);
301 textD
->calltip
.pos
= -1;
303 /* Store the x-offset for use when redrawing */
304 textD
->calltip
.pos
= rel_x
;
307 /* Should really bounds-check these enumerations... */
308 textD
->calltip
.ID
= StaticCalltipID
;
309 textD
->calltip
.anchored
= anchored
;
310 textD
->calltip
.hAlign
= hAlign
;
311 textD
->calltip
.vAlign
= vAlign
;
312 textD
->calltip
.alignMode
= alignMode
;
314 /* Increment the static calltip ID. Macro variables can only be int,
315 not unsigned, so have to work to keep it > 0 on overflow */
316 if(++StaticCalltipID
<= 0)
319 /* Realize the calltip's shell so that its width & height are known */
320 XtRealizeWidget( textD
->calltipShell
);
321 /* Move the calltip and pop it up */
322 TextDRedrawCalltip(textD
, 0);
323 XtPopup( textD
->calltipShell
, XtGrabNone
);
324 return textD
->calltip
.ID
;