wmaker: Added 'const' attribute to local function 'getMaxStringWidth'
[wmaker-crm.git] / src / balloon.c
blob44e0964e8570fbb640e35f1474ea5684580c8e4d
1 /*
2 * Window Maker window manager
4 * Copyright (c) 1998-2003 Alfredo K. Kojima
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "wconfig.h"
23 #ifdef BALLOON_TEXT
25 #include <stdio.h>
26 #include <X11/Xlib.h>
27 #include <X11/Xutil.h>
28 #ifdef SHAPED_BALLOON
29 #include <X11/extensions/shape.h>
30 #endif
32 #include <stdlib.h>
33 #include <string.h>
35 #include <wraster.h>
37 #include "WindowMaker.h"
38 #include "screen.h"
39 #include "texture.h"
40 #include "framewin.h"
41 #include "icon.h"
42 #include "appicon.h"
43 #include "workspace.h"
44 #include "balloon.h"
46 extern WPreferences wPreferences;
48 typedef struct _WBalloon {
49 Window window;
51 #ifdef SHAPED_BALLOON
52 GC monoGC;
53 #endif
54 int prevType;
56 Window objectWindow;
57 char *text;
58 int h;
60 WMHandlerID timer;
62 Pixmap contents;
64 char mapped;
65 char ignoreTimer;
66 } WBalloon;
68 #define TOP 0
69 #define BOTTOM 1
70 #define LEFT 0
71 #define RIGHT 2
73 #define TLEFT (TOP|LEFT)
74 #define TRIGHT (TOP|RIGHT)
75 #define BLEFT (BOTTOM|LEFT)
76 #define BRIGHT (BOTTOM|RIGHT)
78 static int countLines(const char *text)
80 const char *p = text;
81 int h = 1;
83 while (*p) {
84 if (*p == '\n' && p[1] != 0)
85 h++;
86 p++;
88 return h;
91 static int getMaxStringWidth(WMFont * font, const char *text)
93 const char *p = text;
94 const char *pb = p;
95 int pos = 0;
96 int w = 0, wt;
98 while (*p) {
99 if (*p == '\n') {
100 wt = WMWidthOfString(font, pb, pos);
101 if (wt > w)
102 w = wt;
103 pos = 0;
104 pb = p + 1;
105 } else {
106 pos++;
108 p++;
110 if (pos > 0) {
111 wt = WMWidthOfString(font, pb, pos);
112 if (wt > w)
113 w = wt;
115 return w;
118 static void
119 drawMultiLineString(WMScreen * scr, Pixmap pixmap, WMColor * color,
120 WMFont * font, int x, int y, char *text, int len)
122 char *p = text;
123 char *pb = p;
124 int l = 0, pos = 0;
125 int height = WMFontHeight(font);
127 while (*p && p - text < len) {
128 if (*p == '\n') {
129 WMDrawString(scr, pixmap, color, font, x, y + l * height, pb, pos);
130 l++;
131 pos = 0;
132 pb = p + 1;
133 } else {
134 pos++;
136 p++;
138 if (pos > 0) {
139 WMDrawString(scr, pixmap, color, font, x, y + l * height, pb, pos);
143 #ifdef SHAPED_BALLOON
145 #define SPACE 12
147 static void drawBalloon(WScreen * scr, Pixmap bitmap, Pixmap pix, int x, int y, int w, int h, int side)
149 GC bgc = scr->balloon->monoGC;
150 GC gc = scr->draw_gc;
151 int rad = h * 3 / 10;
152 XPoint pt[3], ipt[3];
153 int w1;
155 /* outline */
156 XSetForeground(dpy, bgc, 1);
158 XFillArc(dpy, bitmap, bgc, x, y, rad, rad, 90 * 64, 90 * 64);
159 XFillArc(dpy, bitmap, bgc, x, y + h - 1 - rad, rad, rad, 180 * 64, 90 * 64);
161 XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y, rad, rad, 0 * 64, 90 * 64);
162 XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y + h - 1 - rad, rad, rad, 270 * 64, 90 * 64);
164 XFillRectangle(dpy, bitmap, bgc, x, y + rad / 2, w, h - rad);
165 XFillRectangle(dpy, bitmap, bgc, x + rad / 2, y, w - rad, h);
167 /* interior */
168 XSetForeground(dpy, gc, scr->white_pixel);
170 XFillArc(dpy, pix, gc, x + 1, y + 1, rad, rad, 90 * 64, 90 * 64);
171 XFillArc(dpy, pix, gc, x + 1, y + h - 2 - rad, rad, rad, 180 * 64, 90 * 64);
173 XFillArc(dpy, pix, gc, x + w - 2 - rad, y + 1, rad, rad, 0 * 64, 90 * 64);
174 XFillArc(dpy, pix, gc, x + w - 2 - rad, y + h - 2 - rad, rad, rad, 270 * 64, 90 * 64);
176 XFillRectangle(dpy, pix, gc, x + 1, y + 1 + rad / 2, w - 2, h - 2 - rad);
177 XFillRectangle(dpy, pix, gc, x + 1 + rad / 2, y + 1, w - 2 - rad, h - 2);
179 if (side & BOTTOM) {
180 pt[0].y = y + h - 1;
181 pt[1].y = y + h - 1 + SPACE;
182 pt[2].y = y + h - 1;
183 ipt[0].y = pt[0].y - 1;
184 ipt[1].y = pt[1].y - 1;
185 ipt[2].y = pt[2].y - 1;
186 } else {
187 pt[0].y = y;
188 pt[1].y = y - SPACE;
189 pt[2].y = y;
190 ipt[0].y = pt[0].y + 1;
191 ipt[1].y = pt[1].y + 1;
192 ipt[2].y = pt[2].y + 1;
195 /*w1 = WMAX(h, 24); */
196 w1 = WMAX(h, 21);
198 if (side & RIGHT) {
199 pt[0].x = x + w - w1 + 2 * w1 / 16;
200 pt[1].x = x + w - w1 + 11 * w1 / 16;
201 pt[2].x = x + w - w1 + 7 * w1 / 16;
202 ipt[0].x = x + 1 + w - w1 + 2 * (w1 - 1) / 16;
203 ipt[1].x = x + 1 + w - w1 + 11 * (w1 - 1) / 16;
204 ipt[2].x = x + 1 + w - w1 + 7 * (w1 - 1) / 16;
205 /*ipt[0].x = pt[0].x+1;
206 ipt[1].x = pt[1].x;
207 ipt[2].x = pt[2].x; */
208 } else {
209 pt[0].x = x + w1 - 2 * w1 / 16;
210 pt[1].x = x + w1 - 11 * w1 / 16;
211 pt[2].x = x + w1 - 7 * w1 / 16;
212 ipt[0].x = x - 1 + w1 - 2 * (w1 - 1) / 16;
213 ipt[1].x = x - 1 + w1 - 11 * (w1 - 1) / 16;
214 ipt[2].x = x - 1 + w1 - 7 * (w1 - 1) / 16;
215 /*ipt[0].x = pt[0].x-1;
216 ipt[1].x = pt[1].x;
217 ipt[2].x = pt[2].x; */
220 XFillPolygon(dpy, bitmap, bgc, pt, 3, Convex, CoordModeOrigin);
221 XFillPolygon(dpy, pix, gc, ipt, 3, Convex, CoordModeOrigin);
223 /* fix outline */
224 XSetForeground(dpy, gc, scr->black_pixel);
226 XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
227 if (side & RIGHT) {
228 pt[0].x++;
229 pt[2].x--;
230 } else {
231 pt[0].x--;
232 pt[2].x++;
234 XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
237 static Pixmap makePixmap(WScreen * scr, int width, int height, int side, Pixmap * mask)
239 WBalloon *bal = scr->balloon;
240 Pixmap bitmap;
241 Pixmap pixmap;
242 int x, y;
244 bitmap = XCreatePixmap(dpy, scr->root_win, width + SPACE, height + SPACE, 1);
246 if (!bal->monoGC) {
247 bal->monoGC = XCreateGC(dpy, bitmap, 0, NULL);
249 XSetForeground(dpy, bal->monoGC, 0);
250 XFillRectangle(dpy, bitmap, bal->monoGC, 0, 0, width + SPACE, height + SPACE);
252 pixmap = XCreatePixmap(dpy, scr->root_win, width + SPACE, height + SPACE, scr->w_depth);
253 XSetForeground(dpy, scr->draw_gc, scr->black_pixel);
254 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width + SPACE, height + SPACE);
256 if (side & BOTTOM) {
257 y = 0;
258 } else {
259 y = SPACE;
261 x = 0;
263 drawBalloon(scr, bitmap, pixmap, x, y, width, height, side);
265 *mask = bitmap;
267 return pixmap;
270 static void showText(WScreen * scr, int x, int y, int h, int w, char *text)
272 int width;
273 int height;
274 Pixmap pixmap;
275 Pixmap mask;
276 WMFont *font = scr->info_text_font;
277 int side = 0;
278 int ty;
279 int bx, by;
281 if (scr->balloon->contents)
282 XFreePixmap(dpy, scr->balloon->contents);
284 width = getMaxStringWidth(font, text) + 16;
285 height = countLines(text) * WMFontHeight(font) + 4;
287 if (height < 16)
288 height = 16;
289 if (width < height)
290 width = height;
292 if (x + width > scr->scr_width) {
293 side = RIGHT;
294 bx = x - width + w / 2;
295 if (bx < 0)
296 bx = 0;
297 } else {
298 side = LEFT;
299 bx = x + w / 2;
301 if (bx + width > scr->scr_width)
302 bx = scr->scr_width - width;
304 if (y - (height + SPACE) < 0) {
305 side |= TOP;
306 by = y + h - 1;
307 ty = SPACE;
308 } else {
309 side |= BOTTOM;
310 by = y - (height + SPACE);
311 ty = 0;
313 pixmap = makePixmap(scr, width, height, side, &mask);
315 drawMultiLineString(scr->wmscreen, pixmap, scr->black, font, 8, ty + 2, text, strlen(text));
317 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
318 scr->balloon->contents = pixmap;
320 XResizeWindow(dpy, scr->balloon->window, width, height + SPACE);
321 XShapeCombineMask(dpy, scr->balloon->window, ShapeBounding, 0, 0, mask, ShapeSet);
322 XFreePixmap(dpy, mask);
323 XMoveWindow(dpy, scr->balloon->window, bx, by);
324 XMapRaised(dpy, scr->balloon->window);
326 scr->balloon->mapped = 1;
328 #else /* !SHAPED_BALLOON */
330 static void showText(WScreen * scr, int x, int y, int h, int w, char *text)
332 int width;
333 int height;
334 Pixmap pixmap;
335 WMFont *font = scr->info_text_font;
337 if (scr->balloon->contents)
338 XFreePixmap(dpy, scr->balloon->contents);
340 width = getMaxStringWidth(font, text) + 8;
341 /*width = WMWidthOfString(font, text, strlen(text))+8; */
342 height = countLines(text) * WMFontHeight(font) + 4;
344 if (x < 0)
345 x = 0;
346 else if (x + width > scr->scr_width - 1)
347 x = scr->scr_width - width;
349 if (y - height - 2 < 0) {
350 y += h;
351 if (y < 0)
352 y = 0;
353 } else {
354 y -= height + 2;
357 if (scr->window_title_texture[0])
358 XSetForeground(dpy, scr->draw_gc, scr->window_title_texture[0]->any.color.pixel);
359 else
360 XSetForeground(dpy, scr->draw_gc, scr->light_pixel);
362 pixmap = XCreatePixmap(dpy, scr->root_win, width, height, scr->w_depth);
363 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width, height);
365 drawMultiLineString(scr->wmscreen, pixmap, scr->window_title_color[0], font, 4, 2, text, strlen(text));
367 XResizeWindow(dpy, scr->balloon->window, width, height);
368 XMoveWindow(dpy, scr->balloon->window, x, y);
370 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
371 XClearWindow(dpy, scr->balloon->window);
372 XMapRaised(dpy, scr->balloon->window);
374 scr->balloon->contents = pixmap;
376 scr->balloon->mapped = 1;
378 #endif /* !SHAPED_BALLOON */
380 static void showBalloon(WScreen * scr)
382 int x, y;
383 Window foow;
384 unsigned foo, w;
386 if (scr->balloon) {
387 scr->balloon->timer = NULL;
388 scr->balloon->ignoreTimer = 1;
391 if (!XGetGeometry(dpy, scr->balloon->objectWindow, &foow, &x, &y, &w, &foo, &foo, &foo)) {
392 scr->balloon->prevType = 0;
393 return;
395 showText(scr, x, y, scr->balloon->h, w, scr->balloon->text);
398 static void frameBalloon(WObjDescriptor * object)
400 WFrameWindow *fwin = (WFrameWindow *) object->parent;
401 WScreen *scr = fwin->core->screen_ptr;
403 if (fwin->titlebar != object->self || !fwin->flags.is_client_window_frame) {
404 wBalloonHide(scr);
405 return;
407 if (fwin->title && fwin->flags.incomplete_title) {
408 scr->balloon->h = (fwin->titlebar ? fwin->titlebar->height : 0);
409 scr->balloon->text = wstrdup(fwin->title);
410 scr->balloon->objectWindow = fwin->core->window;
411 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
415 static void miniwindowBalloon(WObjDescriptor * object)
417 WIcon *icon = (WIcon *) object->parent;
418 WScreen *scr = icon->core->screen_ptr;
420 if (!icon->icon_name) {
421 wBalloonHide(scr);
422 return;
424 scr->balloon->h = icon->core->height;
425 scr->balloon->text = wstrdup(icon->icon_name);
426 scr->balloon->objectWindow = icon->core->window;
427 if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_APPICON)
428 && scr->balloon->ignoreTimer) {
429 XUnmapWindow(dpy, scr->balloon->window);
430 showBalloon(scr);
431 } else {
432 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
436 static void appiconBalloon(WObjDescriptor * object)
438 WAppIcon *aicon = (WAppIcon *) object->parent;
439 WScreen *scr = aicon->icon->core->screen_ptr;
440 char *tmp;
442 /* Show balloon if it is the Clip and the workspace name is > 5 chars */
443 if (object->parent == scr->clip_icon) {
444 if (strlen(scr->workspaces[scr->current_workspace]->name) > 5) {
445 scr->balloon->text = wstrdup(scr->workspaces[scr->current_workspace]->name);
446 } else {
447 wBalloonHide(scr);
448 return;
450 } else if (aicon->command && aicon->wm_class) {
451 int len = strlen(aicon->command) + strlen(aicon->wm_class) + 8;
452 tmp = wmalloc(len);
453 snprintf(tmp, len, "%s\n(%s)", aicon->wm_class, aicon->command);
454 scr->balloon->text = tmp;
455 } else if (aicon->command) {
456 scr->balloon->text = wstrdup(aicon->command);
457 } else if (aicon->wm_class) {
458 scr->balloon->text = wstrdup(aicon->wm_class);
459 } else {
460 wBalloonHide(scr);
461 return;
463 scr->balloon->h = aicon->icon->core->height - 2;
465 scr->balloon->objectWindow = aicon->icon->core->window;
466 if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_MINIWINDOW)
467 && scr->balloon->ignoreTimer) {
468 XUnmapWindow(dpy, scr->balloon->window);
469 showBalloon(scr);
470 } else {
471 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
475 void wBalloonInitialize(WScreen * scr)
477 WBalloon *bal;
478 XSetWindowAttributes attribs;
479 unsigned long vmask;
481 bal = wmalloc(sizeof(WBalloon));
483 scr->balloon = bal;
485 vmask = CWSaveUnder | CWOverrideRedirect | CWColormap | CWBackPixel | CWBorderPixel;
486 attribs.save_under = True;
487 attribs.override_redirect = True;
488 attribs.colormap = scr->w_colormap;
489 attribs.background_pixel = scr->icon_back_texture->normal.pixel;
490 attribs.border_pixel = 0; /* do not care */
492 bal->window = XCreateWindow(dpy, scr->root_win, 1, 1, 10, 10, 1,
493 scr->w_depth, CopyFromParent, scr->w_visual, vmask, &attribs);
494 #if 0
495 /* select EnterNotify to so that the balloon will be unmapped
496 * when the pointer is moved over it */
497 XSelectInput(dpy, bal->window, EnterWindowMask);
498 #endif
501 void wBalloonEnteredObject(WScreen * scr, WObjDescriptor * object)
503 WBalloon *balloon = scr->balloon;
505 if (balloon->timer) {
506 WMDeleteTimerHandler(balloon->timer);
507 balloon->timer = NULL;
508 balloon->ignoreTimer = 0;
511 if (scr->balloon->text)
512 wfree(scr->balloon->text);
513 scr->balloon->text = NULL;
515 if (!object) {
516 wBalloonHide(scr);
517 balloon->ignoreTimer = 0;
518 return;
521 switch (object->parent_type) {
522 case WCLASS_FRAME:
523 if (wPreferences.window_balloon)
524 frameBalloon(object);
525 break;
526 case WCLASS_DOCK_ICON:
527 if (wPreferences.appicon_balloon)
528 appiconBalloon(object);
529 break;
530 case WCLASS_MINIWINDOW:
531 if (wPreferences.miniwin_balloon)
532 miniwindowBalloon(object);
533 break;
534 case WCLASS_APPICON:
535 if (wPreferences.appicon_balloon)
536 appiconBalloon(object);
537 break;
538 default:
539 wBalloonHide(scr);
540 break;
542 scr->balloon->prevType = object->parent_type;
545 void wBalloonHide(WScreen * scr)
547 if (scr) {
548 if (scr->balloon->mapped) {
549 XUnmapWindow(dpy, scr->balloon->window);
550 scr->balloon->mapped = 0;
551 } else if (scr->balloon->timer) {
552 WMDeleteTimerHandler(scr->balloon->timer);
553 scr->balloon->timer = NULL;
555 scr->balloon->prevType = 0;
559 #endif