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 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 )
109 if( calltipID
!= 0 && calltipID
!= textD
->calltip
.ID
)
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
);
123 if (textD
->calltip
.pos
< 0) {
124 /* First display of tip with cursor offscreen (detected in
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
);
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
)
146 else if (textD
->calltip
.hAlign
== TIP_RIGHT
)
149 /* Adjust rel_y for vertical alignment modes */
150 if (textD
->calltip
.vAlign
== TIP_ABOVE
) {
151 flip_delta
= tipHeight
+ lineHeight
+ 2*borderWidth
;
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
))
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
) {
196 char *c
, *cCpy
, *textCpy
;
198 /* First count 'em */
199 for( c
= text
; *c
; ++c
)
205 /* Allocate the new string */
206 len
= strlen( text
) + ( tab_width
- 1 )*nTabs
;
207 textCpy
= (char*)malloc( len
+ 1 );
210 "nedit: Out of heap memory in expandAllTabs!\n");
214 /* Now replace 'em */
215 for( c
= text
, cCpy
= textCpy
; *c
; ++c
, ++cCpy
) {
217 for( i
= 0; i
< tab_width
; ++i
, ++cCpy
)
219 --cCpy
; /* Will be incremented in outer for loop */
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
;
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
)
255 /* Get the location/dimensions of the text area */
256 XtVaGetValues(textD
->w
,
261 /* Create the calltip widget on first request */
262 if (textD
->calltipW
== NULL
) {
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
,
283 /* Set the text on the label */
284 XtVaSetValues( textD
->calltipW
, XmNlabelString
, str
, NULL
);
287 /* Figure out where to put the tip */
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);
295 textD
->calltip
.pos
= pos
;
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);
304 textD
->calltip
.pos
= -1;
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)
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
;