wmaker: replace and be replaced (ICCCM protocol)
[wmaker-crm.git] / src / balloon.c
blob1e05ca70486524cd79c1343ed589fa5587430ba9
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 mini_preview;
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 static int countLines(const char *text)
77 const char *p = text;
78 int h = 1;
80 while (*p) {
81 if (*p == '\n' && p[1] != 0)
82 h++;
83 p++;
85 return h;
88 static int getMaxStringWidth(WMFont *font, const char *text)
90 const char *p = text;
91 const char *pb = p;
92 int pos = 0;
93 int w = 0, wt;
95 while (*p) {
96 if (*p == '\n') {
97 wt = WMWidthOfString(font, pb, pos);
98 if (wt > w)
99 w = wt;
100 pos = 0;
101 pb = p + 1;
102 } else {
103 pos++;
105 p++;
107 if (pos > 0) {
108 wt = WMWidthOfString(font, pb, pos);
109 if (wt > w)
110 w = wt;
112 return w;
115 static void drawMultiLineString(WMScreen *scr, Pixmap pixmap, WMColor *color,
116 WMFont *font, int x, int y, const char *text, int len)
118 const char *p = text;
119 const char *pb = p;
120 int l = 0, pos = 0;
121 int height = WMFontHeight(font);
123 while (*p && p - text < len) {
124 if (*p == '\n') {
125 WMDrawString(scr, pixmap, color, font, x, y + l * height, pb, pos);
126 l++;
127 pos = 0;
128 pb = p + 1;
129 } else {
130 pos++;
132 p++;
134 if (pos > 0)
135 WMDrawString(scr, pixmap, color, font, x, y + l * height, pb, pos);
138 #ifdef SHAPED_BALLOON
140 #define SPACE 12
142 static void drawBalloon(WScreen *scr, Pixmap bitmap, Pixmap pix, int x, int y, int w, int h, int side)
144 GC bgc = scr->balloon->monoGC;
145 GC gc = scr->draw_gc;
146 int rad = h * 3 / 10;
147 XPoint pt[3], ipt[3];
148 int w1;
150 /* outline */
151 XSetForeground(dpy, bgc, 1);
153 XFillArc(dpy, bitmap, bgc, x, y, rad, rad, 90 * 64, 90 * 64);
154 XFillArc(dpy, bitmap, bgc, x, y + h - 1 - rad, rad, rad, 180 * 64, 90 * 64);
156 XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y, rad, rad, 0 * 64, 90 * 64);
157 XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y + h - 1 - rad, rad, rad, 270 * 64, 90 * 64);
159 XFillRectangle(dpy, bitmap, bgc, x, y + rad / 2, w, h - rad);
160 XFillRectangle(dpy, bitmap, bgc, x + rad / 2, y, w - rad, h);
162 /* interior */
163 XSetForeground(dpy, gc, scr->white_pixel);
165 XFillArc(dpy, pix, gc, x + 1, y + 1, rad, rad, 90 * 64, 90 * 64);
166 XFillArc(dpy, pix, gc, x + 1, y + h - 2 - rad, rad, rad, 180 * 64, 90 * 64);
168 XFillArc(dpy, pix, gc, x + w - 2 - rad, y + 1, rad, rad, 0 * 64, 90 * 64);
169 XFillArc(dpy, pix, gc, x + w - 2 - rad, y + h - 2 - rad, rad, rad, 270 * 64, 90 * 64);
171 XFillRectangle(dpy, pix, gc, x + 1, y + 1 + rad / 2, w - 2, h - 2 - rad);
172 XFillRectangle(dpy, pix, gc, x + 1 + rad / 2, y + 1, w - 2 - rad, h - 2);
174 if (side & BOTTOM) {
175 pt[0].y = y + h - 1;
176 pt[1].y = y + h - 1 + SPACE;
177 pt[2].y = y + h - 1;
178 ipt[0].y = pt[0].y - 1;
179 ipt[1].y = pt[1].y - 1;
180 ipt[2].y = pt[2].y - 1;
181 } else {
182 pt[0].y = y;
183 pt[1].y = y - SPACE;
184 pt[2].y = y;
185 ipt[0].y = pt[0].y + 1;
186 ipt[1].y = pt[1].y + 1;
187 ipt[2].y = pt[2].y + 1;
190 /*w1 = WMAX(h, 24); */
191 w1 = WMAX(h, 21);
193 if (side & RIGHT) {
194 pt[0].x = x + w - w1 + 2 * w1 / 16;
195 pt[1].x = x + w - w1 + 11 * w1 / 16;
196 pt[2].x = x + w - w1 + 7 * w1 / 16;
197 ipt[0].x = x + 1 + w - w1 + 2 * (w1 - 1) / 16;
198 ipt[1].x = x + 1 + w - w1 + 11 * (w1 - 1) / 16;
199 ipt[2].x = x + 1 + w - w1 + 7 * (w1 - 1) / 16;
200 /*ipt[0].x = pt[0].x+1;
201 ipt[1].x = pt[1].x;
202 ipt[2].x = pt[2].x; */
203 } else {
204 pt[0].x = x + w1 - 2 * w1 / 16;
205 pt[1].x = x + w1 - 11 * w1 / 16;
206 pt[2].x = x + w1 - 7 * w1 / 16;
207 ipt[0].x = x - 1 + w1 - 2 * (w1 - 1) / 16;
208 ipt[1].x = x - 1 + w1 - 11 * (w1 - 1) / 16;
209 ipt[2].x = x - 1 + w1 - 7 * (w1 - 1) / 16;
210 /*ipt[0].x = pt[0].x-1;
211 ipt[1].x = pt[1].x;
212 ipt[2].x = pt[2].x; */
215 XFillPolygon(dpy, bitmap, bgc, pt, 3, Convex, CoordModeOrigin);
216 XFillPolygon(dpy, pix, gc, ipt, 3, Convex, CoordModeOrigin);
218 /* fix outline */
219 XSetForeground(dpy, gc, scr->black_pixel);
221 XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
222 if (side & RIGHT) {
223 pt[0].x++;
224 pt[2].x--;
225 } else {
226 pt[0].x--;
227 pt[2].x++;
229 XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
232 static Pixmap makePixmap(WScreen *scr, int width, int height, int side, Pixmap *mask)
234 WBalloon *bal = scr->balloon;
235 Pixmap bitmap;
236 Pixmap pixmap;
237 int x, y;
239 bitmap = XCreatePixmap(dpy, scr->root_win, width + SPACE, height + SPACE, 1);
241 if (!bal->monoGC)
242 bal->monoGC = XCreateGC(dpy, bitmap, 0, NULL);
244 XSetForeground(dpy, bal->monoGC, 0);
245 XFillRectangle(dpy, bitmap, bal->monoGC, 0, 0, width + SPACE, height + SPACE);
247 pixmap = XCreatePixmap(dpy, scr->root_win, width + SPACE, height + SPACE, scr->w_depth);
248 XSetForeground(dpy, scr->draw_gc, scr->black_pixel);
249 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width + SPACE, height + SPACE);
251 if (side & BOTTOM)
252 y = 0;
253 else
254 y = SPACE;
255 x = 0;
257 drawBalloon(scr, bitmap, pixmap, x, y, width, height, side);
259 *mask = bitmap;
261 return pixmap;
264 static void showText(WScreen *scr, int x, int y, int h, int w, const char *text)
266 int width;
267 int height;
268 Pixmap pixmap;
269 Pixmap mask;
270 WMFont *font = scr->info_text_font;
271 int side = 0;
272 int ty;
273 int bx, by;
275 if (scr->balloon->contents)
276 XFreePixmap(dpy, scr->balloon->contents);
278 width = getMaxStringWidth(font, text) + 16;
279 height = countLines(text) * WMFontHeight(font) + 4;
281 if (height < 16)
282 height = 16;
283 if (width < height)
284 width = height;
286 if (x + width > scr->scr_width) {
287 side = RIGHT;
288 bx = x - width + w / 2;
289 if (bx < 0)
290 bx = 0;
291 } else {
292 side = LEFT;
293 bx = x + w / 2;
295 if (bx + width > scr->scr_width)
296 bx = scr->scr_width - width;
298 if (y - (height + SPACE) < 0) {
299 side |= TOP;
300 by = y + h - 1;
301 ty = SPACE;
302 } else {
303 side |= BOTTOM;
304 by = y - (height + SPACE);
305 ty = 0;
307 pixmap = makePixmap(scr, width, height, side, &mask);
309 drawMultiLineString(scr->wmscreen, pixmap, scr->black, font, 8, ty + 2, text, strlen(text));
311 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
312 scr->balloon->contents = pixmap;
314 XResizeWindow(dpy, scr->balloon->window, width, height + SPACE);
315 XShapeCombineMask(dpy, scr->balloon->window, ShapeBounding, 0, 0, mask, ShapeSet);
316 XFreePixmap(dpy, mask);
317 XMoveWindow(dpy, scr->balloon->window, bx, by);
318 XMapRaised(dpy, scr->balloon->window);
320 scr->balloon->mapped = 1;
322 #else /* !SHAPED_BALLOON */
324 static void showText(WScreen *scr, int x, int y, int h, int w, const char *text)
326 int width;
327 int height;
328 Pixmap pixmap;
329 WMFont *font = scr->info_text_font;
331 if (scr->balloon->contents)
332 XFreePixmap(dpy, scr->balloon->contents);
334 width = getMaxStringWidth(font, text) + 8;
335 /*width = WMWidthOfString(font, text, strlen(text))+8; */
336 height = countLines(text) * WMFontHeight(font) + 4;
338 if (x < 0)
339 x = 0;
340 else if (x + width > scr->scr_width - 1)
341 x = scr->scr_width - width;
343 if (y - height - 2 < 0) {
344 y += h;
345 if (y < 0)
346 y = 0;
347 } else {
348 y -= height + 2;
351 if (scr->window_title_texture[0])
352 XSetForeground(dpy, scr->draw_gc, scr->window_title_texture[0]->any.color.pixel);
353 else
354 XSetForeground(dpy, scr->draw_gc, scr->light_pixel);
356 pixmap = XCreatePixmap(dpy, scr->root_win, width, height, scr->w_depth);
357 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width, height);
359 drawMultiLineString(scr->wmscreen, pixmap, scr->window_title_color[0], font, 4, 2, text, strlen(text));
361 XResizeWindow(dpy, scr->balloon->window, width, height);
362 XMoveWindow(dpy, scr->balloon->window, x, y);
364 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
365 XClearWindow(dpy, scr->balloon->window);
366 XMapRaised(dpy, scr->balloon->window);
368 scr->balloon->contents = pixmap;
370 scr->balloon->mapped = 1;
372 #endif /* !SHAPED_BALLOON */
374 static void show_minipreview(WScreen *scr, int x, int y, const char *title, Pixmap mini_preview)
376 Pixmap pixmap;
377 WMFont *font = scr->info_text_font;
378 int width, height;
379 int titleHeight;
380 char *shortenTitle;
382 if (scr->balloon->contents)
383 XFreePixmap(dpy, scr->balloon->contents);
385 width = wPreferences.minipreview_size;
386 height = wPreferences.minipreview_size;
388 if (wPreferences.miniwin_title_balloon) {
389 shortenTitle = ShrinkString(font, title, width - MINIPREVIEW_BORDER * 2);
390 titleHeight = countLines(shortenTitle) * WMFontHeight(font) + 4;
391 height += titleHeight;
392 } else {
393 shortenTitle = NULL;
394 titleHeight = 0;
397 if (x < 0)
398 x = 0;
399 else if (x + width > scr->scr_width - 1)
400 x = scr->scr_width - width - 1;
402 if (y - height - 2 < 0) {
403 y += wPreferences.icon_size;
404 if (y < 0)
405 y = 0;
406 } else {
407 y -= height + 2;
410 if (scr->window_title_texture[0])
411 XSetForeground(dpy, scr->draw_gc, scr->window_title_texture[0]->any.color.pixel);
412 else
413 XSetForeground(dpy, scr->draw_gc, scr->light_pixel);
415 pixmap = XCreatePixmap(dpy, scr->root_win, width, height, scr->w_depth);
416 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width, height);
418 if (shortenTitle != NULL) {
419 drawMultiLineString(scr->wmscreen, pixmap, scr->window_title_color[0], font,
420 MINIPREVIEW_BORDER, MINIPREVIEW_BORDER, shortenTitle, strlen(shortenTitle));
421 wfree(shortenTitle);
424 XCopyArea(dpy, mini_preview, pixmap, scr->draw_gc,
425 0, 0, (wPreferences.minipreview_size - 1 - MINIPREVIEW_BORDER * 2),
426 (wPreferences.minipreview_size - 1 - MINIPREVIEW_BORDER * 2),
427 MINIPREVIEW_BORDER, MINIPREVIEW_BORDER + titleHeight);
429 #ifdef SHAPED_BALLOON
430 XShapeCombineMask(dpy, scr->balloon->window, ShapeBounding, 0, 0, None, ShapeSet);
431 #endif
432 XResizeWindow(dpy, scr->balloon->window, width, height);
433 XMoveWindow(dpy, scr->balloon->window, x, y);
435 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
437 XClearWindow(dpy, scr->balloon->window);
438 XMapRaised(dpy, scr->balloon->window);
441 scr->balloon->contents = pixmap;
443 scr->balloon->mapped = 1;
446 static void showBalloon(WScreen *scr)
448 int x, y;
449 Window foow;
450 unsigned foo, w;
452 scr->balloon->timer = NULL;
453 scr->balloon->ignoreTimer = 1;
455 if (!XGetGeometry(dpy, scr->balloon->objectWindow, &foow, &x, &y, &w, &foo, &foo, &foo)) {
456 scr->balloon->prevType = 0;
457 return;
460 if (wPreferences.miniwin_preview_balloon && scr->balloon->mini_preview != None)
461 /* used to display either the mini-preview alone or the mini-preview with the title */
462 show_minipreview(scr, x, y, scr->balloon->text, scr->balloon->mini_preview);
463 else
464 showText(scr, x, y, scr->balloon->h, w, scr->balloon->text);
467 static void frameBalloon(WObjDescriptor *object)
469 WFrameWindow *fwin = (WFrameWindow *) object->parent;
470 WScreen *scr = fwin->core->screen_ptr;
472 if (fwin->titlebar != object->self || !fwin->flags.is_client_window_frame) {
473 wBalloonHide(scr);
474 return;
476 if (fwin->title && fwin->flags.incomplete_title) {
477 scr->balloon->h = (fwin->titlebar ? fwin->titlebar->height : 0);
478 scr->balloon->text = wstrdup(fwin->title);
479 scr->balloon->objectWindow = fwin->core->window;
480 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
484 static void miniwindowBalloon(WObjDescriptor *object)
486 WIcon *icon = (WIcon *) object->parent;
487 WScreen *scr = icon->core->screen_ptr;
489 if (!icon->icon_name) {
490 wBalloonHide(scr);
491 return;
493 scr->balloon->h = icon->core->height;
494 scr->balloon->text = wstrdup(icon->icon_name);
495 scr->balloon->mini_preview = icon->mini_preview;
496 scr->balloon->objectWindow = icon->core->window;
498 if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_APPICON)
499 && scr->balloon->ignoreTimer) {
500 XUnmapWindow(dpy, scr->balloon->window);
501 showBalloon(scr);
502 } else {
503 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
507 static void appiconBalloon(WObjDescriptor *object)
509 WAppIcon *aicon = (WAppIcon *) object->parent;
510 WScreen *scr = aicon->icon->core->screen_ptr;
511 char *tmp;
513 /* Show balloon if it is the Clip and the workspace name is > 5 chars */
514 if (object->parent == scr->clip_icon) {
515 if (strlen(scr->workspaces[scr->current_workspace]->name) > 5) {
516 scr->balloon->text = wstrdup(scr->workspaces[scr->current_workspace]->name);
517 } else {
518 wBalloonHide(scr);
519 return;
521 } else if (aicon->command && aicon->wm_class) {
522 int len;
523 WApplication *app;
524 unsigned int app_win_cnt = 0;
525 const char *display_name;
527 if (object->parent_type == WCLASS_DOCK_ICON) {
528 if (aicon->main_window) {
529 app = wApplicationOf(aicon->main_window);
530 if (app && app->main_window_desc && app->main_window_desc->fake_group)
531 app_win_cnt = app->main_window_desc->fake_group->retainCount - 1;
536 * Check to see if it is a GNUstep app, because in this case we use the instance
537 * instead of the class, otherwise the user will not be able to distinguish what
538 * is being refered.
540 if (strcmp(aicon->wm_class, "GNUstep") == 0)
541 display_name = aicon->wm_instance;
542 else
543 display_name = aicon->wm_class;
545 len = strlen(aicon->command) + strlen(display_name) + 8;
547 if (app_win_cnt > 0)
548 len += 1 + snprintf(NULL, 0, "%u", app_win_cnt);
550 tmp = wmalloc(len);
551 if (app_win_cnt > 0)
552 snprintf(tmp, len, "%u %s\n(%s)", app_win_cnt, display_name, aicon->command);
553 else
554 snprintf(tmp, len, "%s\n(%s)", aicon->wm_instance, aicon->command);
555 scr->balloon->text = tmp;
557 } else if (aicon->command) {
558 scr->balloon->text = wstrdup(aicon->command);
559 } else if (aicon->wm_class) {
560 /* Check to see if it is a GNUstep App */
561 if (strcmp(aicon->wm_class, "GNUstep") == 0)
562 scr->balloon->text = wstrdup(aicon->wm_instance);
563 else
564 scr->balloon->text = wstrdup(aicon->wm_class);
565 } else {
566 wBalloonHide(scr);
567 return;
569 scr->balloon->h = aicon->icon->core->height - 2;
571 scr->balloon->objectWindow = aicon->icon->core->window;
572 if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_MINIWINDOW)
573 && scr->balloon->ignoreTimer) {
574 XUnmapWindow(dpy, scr->balloon->window);
575 showBalloon(scr);
576 } else {
577 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
581 void wBalloonInitialize(WScreen *scr)
583 WBalloon *bal;
584 XSetWindowAttributes attribs;
585 unsigned long vmask;
587 bal = wmalloc(sizeof(WBalloon));
589 scr->balloon = bal;
591 vmask = CWSaveUnder | CWOverrideRedirect | CWColormap | CWBackPixel | CWBorderPixel;
592 attribs.save_under = True;
593 attribs.override_redirect = True;
594 attribs.colormap = scr->w_colormap;
595 attribs.background_pixel = scr->icon_back_texture->normal.pixel;
596 attribs.border_pixel = 0; /* do not care */
598 bal->window = XCreateWindow(dpy, scr->root_win, 1, 1, 10, 10, 1,
599 scr->w_depth, CopyFromParent, scr->w_visual, vmask, &attribs);
600 #if 0
601 /* select EnterNotify to so that the balloon will be unmapped
602 * when the pointer is moved over it */
603 XSelectInput(dpy, bal->window, EnterWindowMask);
604 #endif
607 void wBalloonEnteredObject(WScreen *scr, WObjDescriptor *object)
609 WBalloon *balloon = scr->balloon;
611 if (balloon->timer) {
612 WMDeleteTimerHandler(balloon->timer);
613 balloon->timer = NULL;
614 balloon->ignoreTimer = 0;
617 if (scr->balloon->text)
618 wfree(scr->balloon->text);
619 scr->balloon->text = NULL;
621 scr->balloon->mini_preview = None;
623 if (!object) {
624 wBalloonHide(scr);
625 balloon->ignoreTimer = 0;
626 return;
629 switch (object->parent_type) {
630 case WCLASS_FRAME:
631 if (wPreferences.window_balloon)
632 frameBalloon(object);
633 break;
634 case WCLASS_DOCK_ICON:
635 if (wPreferences.appicon_balloon)
636 appiconBalloon(object);
637 break;
638 case WCLASS_MINIWINDOW:
639 if (wPreferences.miniwin_title_balloon || wPreferences.miniwin_preview_balloon)
640 miniwindowBalloon(object);
641 break;
642 case WCLASS_APPICON:
643 if (wPreferences.appicon_balloon)
644 appiconBalloon(object);
645 break;
646 default:
647 wBalloonHide(scr);
648 break;
650 scr->balloon->prevType = object->parent_type;
653 void wBalloonHide(WScreen *scr)
655 if (scr) {
656 if (scr->balloon->mapped) {
657 XUnmapWindow(dpy, scr->balloon->window);
658 scr->balloon->mapped = 0;
659 } else if (scr->balloon->timer) {
660 WMDeleteTimerHandler(scr->balloon->timer);
661 scr->balloon->timer = NULL;
663 scr->balloon->prevType = 0;
667 #endif