UNIXworkcode

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