wmaker: replaced macro by an inline function, in X Modifier initialisation
[wmaker-crm.git] / src / balloon.c
blobc989803ecc1cf9bf1ba56c9948552835b6676bda
1 /*
2 * Window Maker window manager
4 * Copyright (c) 1998-2003 Alfredo K. Kojima
5 * Copyright (c) 2014 Window Maker Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "wconfig.h"
24 #ifdef BALLOON_TEXT
26 #include <stdio.h>
27 #include <X11/Xlib.h>
28 #include <X11/Xutil.h>
29 #ifdef SHAPED_BALLOON
30 #include <X11/extensions/shape.h>
31 #endif
33 #include <stdlib.h>
34 #include <string.h>
36 #include <wraster.h>
38 #include "WindowMaker.h"
39 #include "screen.h"
40 #include "texture.h"
41 #include "framewin.h"
42 #include "icon.h"
43 #include "appicon.h"
44 #include "workspace.h"
45 #include "balloon.h"
46 #include "misc.h"
49 typedef struct _WBalloon {
50 Window window;
52 #ifdef SHAPED_BALLOON
53 GC monoGC;
54 #endif
55 int prevType;
57 Window objectWindow;
58 char *text;
59 int h;
61 WMHandlerID timer;
63 Pixmap contents;
64 Pixmap apercu;
66 char mapped;
67 char ignoreTimer;
68 } WBalloon;
70 #define TOP 0
71 #define BOTTOM 1
72 #define LEFT 0
73 #define RIGHT 2
75 #define TLEFT (TOP|LEFT)
76 #define TRIGHT (TOP|RIGHT)
77 #define BLEFT (BOTTOM|LEFT)
78 #define BRIGHT (BOTTOM|RIGHT)
80 static int countLines(const char *text)
82 const char *p = text;
83 int h = 1;
85 while (*p) {
86 if (*p == '\n' && p[1] != 0)
87 h++;
88 p++;
90 return h;
93 static int getMaxStringWidth(WMFont *font, const char *text)
95 const char *p = text;
96 const char *pb = p;
97 int pos = 0;
98 int w = 0, wt;
100 while (*p) {
101 if (*p == '\n') {
102 wt = WMWidthOfString(font, pb, pos);
103 if (wt > w)
104 w = wt;
105 pos = 0;
106 pb = p + 1;
107 } else {
108 pos++;
110 p++;
112 if (pos > 0) {
113 wt = WMWidthOfString(font, pb, pos);
114 if (wt > w)
115 w = wt;
117 return w;
120 static void drawMultiLineString(WMScreen *scr, Pixmap pixmap, WMColor *color,
121 WMFont *font, int x, int y, const char *text, int len)
123 const char *p = text;
124 const char *pb = p;
125 int l = 0, pos = 0;
126 int height = WMFontHeight(font);
128 while (*p && p - text < len) {
129 if (*p == '\n') {
130 WMDrawString(scr, pixmap, color, font, x, y + l * height, pb, pos);
131 l++;
132 pos = 0;
133 pb = p + 1;
134 } else {
135 pos++;
137 p++;
139 if (pos > 0)
140 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;
260 x = 0;
262 drawBalloon(scr, bitmap, pixmap, x, y, width, height, side);
264 *mask = bitmap;
266 return pixmap;
269 static void showText(WScreen *scr, int x, int y, int h, int w, const char *text)
271 int width;
272 int height;
273 Pixmap pixmap;
274 Pixmap mask;
275 WMFont *font = scr->info_text_font;
276 int side = 0;
277 int ty;
278 int bx, by;
280 if (scr->balloon->contents)
281 XFreePixmap(dpy, scr->balloon->contents);
283 width = getMaxStringWidth(font, text) + 16;
284 height = countLines(text) * WMFontHeight(font) + 4;
286 if (height < 16)
287 height = 16;
288 if (width < height)
289 width = height;
291 if (x + width > scr->scr_width) {
292 side = RIGHT;
293 bx = x - width + w / 2;
294 if (bx < 0)
295 bx = 0;
296 } else {
297 side = LEFT;
298 bx = x + w / 2;
300 if (bx + width > scr->scr_width)
301 bx = scr->scr_width - width;
303 if (y - (height + SPACE) < 0) {
304 side |= TOP;
305 by = y + h - 1;
306 ty = SPACE;
307 } else {
308 side |= BOTTOM;
309 by = y - (height + SPACE);
310 ty = 0;
312 pixmap = makePixmap(scr, width, height, side, &mask);
314 drawMultiLineString(scr->wmscreen, pixmap, scr->black, font, 8, ty + 2, text, strlen(text));
316 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
317 scr->balloon->contents = pixmap;
319 XResizeWindow(dpy, scr->balloon->window, width, height + SPACE);
320 XShapeCombineMask(dpy, scr->balloon->window, ShapeBounding, 0, 0, mask, ShapeSet);
321 XFreePixmap(dpy, mask);
322 XMoveWindow(dpy, scr->balloon->window, bx, by);
323 XMapRaised(dpy, scr->balloon->window);
325 scr->balloon->mapped = 1;
327 #else /* !SHAPED_BALLOON */
329 static void showText(WScreen *scr, int x, int y, int h, int w, const char *text)
331 int width;
332 int height;
333 Pixmap pixmap;
334 WMFont *font = scr->info_text_font;
336 if (scr->balloon->contents)
337 XFreePixmap(dpy, scr->balloon->contents);
339 width = getMaxStringWidth(font, text) + 8;
340 /*width = WMWidthOfString(font, text, strlen(text))+8; */
341 height = countLines(text) * WMFontHeight(font) + 4;
343 if (x < 0)
344 x = 0;
345 else if (x + width > scr->scr_width - 1)
346 x = scr->scr_width - width;
348 if (y - height - 2 < 0) {
349 y += h;
350 if (y < 0)
351 y = 0;
352 } else {
353 y -= height + 2;
356 if (scr->window_title_texture[0])
357 XSetForeground(dpy, scr->draw_gc, scr->window_title_texture[0]->any.color.pixel);
358 else
359 XSetForeground(dpy, scr->draw_gc, scr->light_pixel);
361 pixmap = XCreatePixmap(dpy, scr->root_win, width, height, scr->w_depth);
362 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width, height);
364 drawMultiLineString(scr->wmscreen, pixmap, scr->window_title_color[0], font, 4, 2, text, strlen(text));
366 XResizeWindow(dpy, scr->balloon->window, width, height);
367 XMoveWindow(dpy, scr->balloon->window, x, y);
369 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
370 XClearWindow(dpy, scr->balloon->window);
371 XMapRaised(dpy, scr->balloon->window);
373 scr->balloon->contents = pixmap;
375 scr->balloon->mapped = 1;
377 #endif /* !SHAPED_BALLOON */
379 static void showApercu(WScreen *scr, int x, int y, const char *title, Pixmap apercu)
381 Pixmap pixmap;
382 WMFont *font = scr->info_text_font;
383 int width, height;
384 int titleHeight;
385 char *shortenTitle;
387 if (scr->balloon->contents)
388 XFreePixmap(dpy, scr->balloon->contents);
390 width = wPreferences.apercu_size;
391 height = wPreferences.apercu_size;
393 if (wPreferences.miniwin_title_balloon) {
394 shortenTitle = ShrinkString(font, title, width - APERCU_BORDER * 2);
395 titleHeight = countLines(shortenTitle) * WMFontHeight(font) + 4;
396 height += titleHeight;
397 } else {
398 shortenTitle = NULL;
399 titleHeight = 0;
402 if (x < 0)
403 x = 0;
404 else if (x + width > scr->scr_width - 1)
405 x = scr->scr_width - width - 1;
407 if (y - height - 2 < 0) {
408 y += wPreferences.icon_size;
409 if (y < 0)
410 y = 0;
411 } else {
412 y -= height + 2;
415 if (scr->window_title_texture[0])
416 XSetForeground(dpy, scr->draw_gc, scr->window_title_texture[0]->any.color.pixel);
417 else
418 XSetForeground(dpy, scr->draw_gc, scr->light_pixel);
420 pixmap = XCreatePixmap(dpy, scr->root_win, width, height, scr->w_depth);
421 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width, height);
423 if (shortenTitle != NULL) {
424 drawMultiLineString(scr->wmscreen, pixmap, scr->window_title_color[0], font,
425 APERCU_BORDER, APERCU_BORDER, shortenTitle, strlen(shortenTitle));
426 wfree(shortenTitle);
429 XCopyArea(dpy, apercu, pixmap, scr->draw_gc,
430 0, 0, (wPreferences.apercu_size - 1 - APERCU_BORDER * 2),
431 (wPreferences.apercu_size - 1 - APERCU_BORDER * 2),
432 APERCU_BORDER, APERCU_BORDER + titleHeight);
434 #ifdef SHAPED_BALLOON
435 XShapeCombineMask(dpy, scr->balloon->window, ShapeBounding, 0, 0, None, ShapeSet);
436 #endif
437 XResizeWindow(dpy, scr->balloon->window, width, height);
438 XMoveWindow(dpy, scr->balloon->window, x, y);
440 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
442 XClearWindow(dpy, scr->balloon->window);
443 XMapRaised(dpy, scr->balloon->window);
446 scr->balloon->contents = pixmap;
448 scr->balloon->mapped = 1;
451 static void showBalloon(WScreen *scr)
453 int x, y;
454 Window foow;
455 unsigned foo, w;
457 scr->balloon->timer = NULL;
458 scr->balloon->ignoreTimer = 1;
460 if (!XGetGeometry(dpy, scr->balloon->objectWindow, &foow, &x, &y, &w, &foo, &foo, &foo)) {
461 scr->balloon->prevType = 0;
462 return;
465 if (wPreferences.miniwin_apercu_balloon && scr->balloon->apercu != None)
466 /* used to display either the apercu alone or the apercu and the title */
467 showApercu(scr, x, y, scr->balloon->text, scr->balloon->apercu);
468 else
469 showText(scr, x, y, scr->balloon->h, w, scr->balloon->text);
472 static void frameBalloon(WObjDescriptor *object)
474 WFrameWindow *fwin = (WFrameWindow *) object->parent;
475 WScreen *scr = fwin->core->screen_ptr;
477 if (fwin->titlebar != object->self || !fwin->flags.is_client_window_frame) {
478 wBalloonHide(scr);
479 return;
481 if (fwin->title && fwin->flags.incomplete_title) {
482 scr->balloon->h = (fwin->titlebar ? fwin->titlebar->height : 0);
483 scr->balloon->text = wstrdup(fwin->title);
484 scr->balloon->objectWindow = fwin->core->window;
485 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
489 static void miniwindowBalloon(WObjDescriptor *object)
491 WIcon *icon = (WIcon *) object->parent;
492 WScreen *scr = icon->core->screen_ptr;
494 if (!icon->icon_name) {
495 wBalloonHide(scr);
496 return;
498 scr->balloon->h = icon->core->height;
499 scr->balloon->text = wstrdup(icon->icon_name);
500 scr->balloon->apercu = icon->apercu;
501 scr->balloon->objectWindow = icon->core->window;
503 if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_APPICON)
504 && scr->balloon->ignoreTimer) {
505 XUnmapWindow(dpy, scr->balloon->window);
506 showBalloon(scr);
507 } else {
508 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
512 static void appiconBalloon(WObjDescriptor *object)
514 WAppIcon *aicon = (WAppIcon *) object->parent;
515 WScreen *scr = aicon->icon->core->screen_ptr;
516 char *tmp;
518 /* Show balloon if it is the Clip and the workspace name is > 5 chars */
519 if (object->parent == scr->clip_icon) {
520 if (strlen(scr->workspaces[scr->current_workspace]->name) > 5) {
521 scr->balloon->text = wstrdup(scr->workspaces[scr->current_workspace]->name);
522 } else {
523 wBalloonHide(scr);
524 return;
526 } else if (aicon->command && aicon->wm_class) {
527 int len;
528 WApplication *app;
529 unsigned int app_win_cnt = 0;
531 if (object->parent_type == WCLASS_DOCK_ICON) {
532 if (aicon->main_window) {
533 app = wApplicationOf(aicon->main_window);
534 if (app && app->main_window_desc && app->main_window_desc->fake_group)
535 app_win_cnt = app->main_window_desc->fake_group->retainCount - 1;
539 /* Check to see if it is a GNUstep app */
540 if (strcmp(aicon->wm_class, "GNUstep") == 0)
541 len = strlen(aicon->command) + strlen(aicon->wm_instance) + 8;
542 else
543 len = strlen(aicon->command) + strlen(aicon->wm_class) + 8;
545 if (app_win_cnt > 0)
546 len += 1 + snprintf(NULL, 0, "%u", app_win_cnt);
548 tmp = wmalloc(len);
549 /* Check to see if it is a GNUstep App */
550 if (strcmp(aicon->wm_class, "GNUstep") == 0)
551 if (app_win_cnt > 0)
552 snprintf(tmp, len, "%u %s\n(%s)", app_win_cnt, aicon->wm_instance, aicon->command);
553 else
554 snprintf(tmp, len, "%s\n(%s)", aicon->wm_instance, aicon->command);
555 else
556 if (app_win_cnt > 0)
557 snprintf(tmp, len, "%u %s\n(%s)", app_win_cnt, aicon->wm_class, aicon->command);
558 else
559 snprintf(tmp, len, "%s\n(%s)", aicon->wm_class, aicon->command);
560 scr->balloon->text = tmp;
561 } else if (aicon->command) {
562 scr->balloon->text = wstrdup(aicon->command);
563 } else if (aicon->wm_class) {
564 /* Check to see if it is a GNUstep App */
565 if (strcmp(aicon->wm_class, "GNUstep") == 0)
566 scr->balloon->text = wstrdup(aicon->wm_instance);
567 else
568 scr->balloon->text = wstrdup(aicon->wm_class);
569 } else {
570 wBalloonHide(scr);
571 return;
573 scr->balloon->h = aicon->icon->core->height - 2;
575 scr->balloon->objectWindow = aicon->icon->core->window;
576 if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_MINIWINDOW)
577 && scr->balloon->ignoreTimer) {
578 XUnmapWindow(dpy, scr->balloon->window);
579 showBalloon(scr);
580 } else {
581 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
585 void wBalloonInitialize(WScreen *scr)
587 WBalloon *bal;
588 XSetWindowAttributes attribs;
589 unsigned long vmask;
591 bal = wmalloc(sizeof(WBalloon));
593 scr->balloon = bal;
595 vmask = CWSaveUnder | CWOverrideRedirect | CWColormap | CWBackPixel | CWBorderPixel;
596 attribs.save_under = True;
597 attribs.override_redirect = True;
598 attribs.colormap = scr->w_colormap;
599 attribs.background_pixel = scr->icon_back_texture->normal.pixel;
600 attribs.border_pixel = 0; /* do not care */
602 bal->window = XCreateWindow(dpy, scr->root_win, 1, 1, 10, 10, 1,
603 scr->w_depth, CopyFromParent, scr->w_visual, vmask, &attribs);
604 #if 0
605 /* select EnterNotify to so that the balloon will be unmapped
606 * when the pointer is moved over it */
607 XSelectInput(dpy, bal->window, EnterWindowMask);
608 #endif
611 void wBalloonEnteredObject(WScreen *scr, WObjDescriptor *object)
613 WBalloon *balloon = scr->balloon;
615 if (balloon->timer) {
616 WMDeleteTimerHandler(balloon->timer);
617 balloon->timer = NULL;
618 balloon->ignoreTimer = 0;
621 if (scr->balloon->text)
622 wfree(scr->balloon->text);
623 scr->balloon->text = NULL;
625 scr->balloon->apercu = None;
627 if (!object) {
628 wBalloonHide(scr);
629 balloon->ignoreTimer = 0;
630 return;
633 switch (object->parent_type) {
634 case WCLASS_FRAME:
635 if (wPreferences.window_balloon)
636 frameBalloon(object);
637 break;
638 case WCLASS_DOCK_ICON:
639 if (wPreferences.appicon_balloon)
640 appiconBalloon(object);
641 break;
642 case WCLASS_MINIWINDOW:
643 if (wPreferences.miniwin_title_balloon || wPreferences.miniwin_apercu_balloon)
644 miniwindowBalloon(object);
645 break;
646 case WCLASS_APPICON:
647 if (wPreferences.appicon_balloon)
648 appiconBalloon(object);
649 break;
650 default:
651 wBalloonHide(scr);
652 break;
654 scr->balloon->prevType = object->parent_type;
657 void wBalloonHide(WScreen *scr)
659 if (scr) {
660 if (scr->balloon->mapped) {
661 XUnmapWindow(dpy, scr->balloon->window);
662 scr->balloon->mapped = 0;
663 } else if (scr->balloon->timer) {
664 WMDeleteTimerHandler(scr->balloon->timer);
665 scr->balloon->timer = NULL;
667 scr->balloon->prevType = 0;
671 #endif