Change to the linux kernel coding style
[wmaker-crm.git] / src / balloon.c
1 /*
2  *  Window Maker window manager
3  *
4  *  Copyright (c) 1998-2003 Alfredo K. Kojima
5  *
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.
10  *
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.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
19  *  USA.
20  */
21
22 #include "wconfig.h"
23
24 #ifdef BALLOON_TEXT
25
26 #include <X11/Xlib.h>
27 #include <X11/Xutil.h>
28 #ifdef SHAPED_BALLOON
29 #include <X11/extensions/shape.h>
30 #endif
31
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <wraster.h>
36
37 #include "WindowMaker.h"
38 #include "screen.h"
39 #include "texture.h"
40 #include "wcore.h"
41 #include "framewin.h"
42 #include "icon.h"
43 #include "appicon.h"
44 #include "funcs.h"
45 #include "workspace.h"
46 #include "balloon.h"
47
48 extern WPreferences wPreferences;
49
50 typedef struct _WBalloon {
51         Window window;
52
53 #ifdef SHAPED_BALLOON
54         GC monoGC;
55 #endif
56         int prevType;
57
58         Window objectWindow;
59         char *text;
60         int h;
61
62         WMHandlerID timer;
63
64         Pixmap contents;
65
66         char mapped;
67         char ignoreTimer;
68 } WBalloon;
69
70 #define TOP     0
71 #define BOTTOM  1
72 #define LEFT    0
73 #define RIGHT   2
74
75 #define TLEFT   (TOP|LEFT)
76 #define TRIGHT  (TOP|RIGHT)
77 #define BLEFT   (BOTTOM|LEFT)
78 #define BRIGHT  (BOTTOM|RIGHT)
79
80 static int countLines(const char *text)
81 {
82         const char *p = text;
83         int h = 1;
84
85         while (*p) {
86                 if (*p == '\n' && p[1] != 0)
87                         h++;
88                 p++;
89         }
90         return h;
91 }
92
93 static int getMaxStringWidth(WMFont * font, char *text)
94 {
95         char *p = text;
96         char *pb = p;
97         int pos = 0;
98         int w = 0, wt;
99
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++;
109                 }
110                 p++;
111         }
112         if (pos > 0) {
113                 wt = WMWidthOfString(font, pb, pos);
114                 if (wt > w)
115                         w = wt;
116         }
117         return w;
118 }
119
120 static void
121 drawMultiLineString(WMScreen * scr, Pixmap pixmap, WMColor * color,
122                     WMFont * font, int x, int y, char *text, int len)
123 {
124         char *p = text;
125         char *pb = p;
126         int l = 0, pos = 0;
127         int height = WMFontHeight(font);
128
129         while (*p && p - text < len) {
130                 if (*p == '\n') {
131                         WMDrawString(scr, pixmap, color, font, x, y + l * height, pb, pos);
132                         l++;
133                         pos = 0;
134                         pb = p + 1;
135                 } else {
136                         pos++;
137                 }
138                 p++;
139         }
140         if (pos > 0) {
141                 WMDrawString(scr, pixmap, color, font, x, y + l * height, pb, pos);
142         }
143 }
144
145 #ifdef SHAPED_BALLOON
146
147 #define         SPACE   12
148
149 static void drawBalloon(WScreen * scr, Pixmap bitmap, Pixmap pix, int x, int y, int w, int h, int side)
150 {
151         GC bgc = scr->balloon->monoGC;
152         GC gc = scr->draw_gc;
153         int rad = h * 3 / 10;
154         XPoint pt[3], ipt[3];
155         int w1;
156
157         /* outline */
158         XSetForeground(dpy, bgc, 1);
159
160         XFillArc(dpy, bitmap, bgc, x, y, rad, rad, 90 * 64, 90 * 64);
161         XFillArc(dpy, bitmap, bgc, x, y + h - 1 - rad, rad, rad, 180 * 64, 90 * 64);
162
163         XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y, rad, rad, 0 * 64, 90 * 64);
164         XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y + h - 1 - rad, rad, rad, 270 * 64, 90 * 64);
165
166         XFillRectangle(dpy, bitmap, bgc, x, y + rad / 2, w, h - rad);
167         XFillRectangle(dpy, bitmap, bgc, x + rad / 2, y, w - rad, h);
168
169         /* interior */
170         XSetForeground(dpy, gc, scr->white_pixel);
171
172         XFillArc(dpy, pix, gc, x + 1, y + 1, rad, rad, 90 * 64, 90 * 64);
173         XFillArc(dpy, pix, gc, x + 1, y + h - 2 - rad, rad, rad, 180 * 64, 90 * 64);
174
175         XFillArc(dpy, pix, gc, x + w - 2 - rad, y + 1, rad, rad, 0 * 64, 90 * 64);
176         XFillArc(dpy, pix, gc, x + w - 2 - rad, y + h - 2 - rad, rad, rad, 270 * 64, 90 * 64);
177
178         XFillRectangle(dpy, pix, gc, x + 1, y + 1 + rad / 2, w - 2, h - 2 - rad);
179         XFillRectangle(dpy, pix, gc, x + 1 + rad / 2, y + 1, w - 2 - rad, h - 2);
180
181         if (side & BOTTOM) {
182                 pt[0].y = y + h - 1;
183                 pt[1].y = y + h - 1 + SPACE;
184                 pt[2].y = y + h - 1;
185                 ipt[0].y = pt[0].y - 1;
186                 ipt[1].y = pt[1].y - 1;
187                 ipt[2].y = pt[2].y - 1;
188         } else {
189                 pt[0].y = y;
190                 pt[1].y = y - SPACE;
191                 pt[2].y = y;
192                 ipt[0].y = pt[0].y + 1;
193                 ipt[1].y = pt[1].y + 1;
194                 ipt[2].y = pt[2].y + 1;
195         }
196
197         /*w1 = WMAX(h, 24); */
198         w1 = WMAX(h, 21);
199
200         if (side & RIGHT) {
201                 pt[0].x = x + w - w1 + 2 * w1 / 16;
202                 pt[1].x = x + w - w1 + 11 * w1 / 16;
203                 pt[2].x = x + w - w1 + 7 * w1 / 16;
204                 ipt[0].x = x + 1 + w - w1 + 2 * (w1 - 1) / 16;
205                 ipt[1].x = x + 1 + w - w1 + 11 * (w1 - 1) / 16;
206                 ipt[2].x = x + 1 + w - w1 + 7 * (w1 - 1) / 16;
207                 /*ipt[0].x = pt[0].x+1;
208                    ipt[1].x = pt[1].x;
209                    ipt[2].x = pt[2].x; */
210         } else {
211                 pt[0].x = x + w1 - 2 * w1 / 16;
212                 pt[1].x = x + w1 - 11 * w1 / 16;
213                 pt[2].x = x + w1 - 7 * w1 / 16;
214                 ipt[0].x = x - 1 + w1 - 2 * (w1 - 1) / 16;
215                 ipt[1].x = x - 1 + w1 - 11 * (w1 - 1) / 16;
216                 ipt[2].x = x - 1 + w1 - 7 * (w1 - 1) / 16;
217                 /*ipt[0].x = pt[0].x-1;
218                    ipt[1].x = pt[1].x;
219                    ipt[2].x = pt[2].x; */
220         }
221
222         XFillPolygon(dpy, bitmap, bgc, pt, 3, Convex, CoordModeOrigin);
223         XFillPolygon(dpy, pix, gc, ipt, 3, Convex, CoordModeOrigin);
224
225         /* fix outline */
226         XSetForeground(dpy, gc, scr->black_pixel);
227
228         XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
229         if (side & RIGHT) {
230                 pt[0].x++;
231                 pt[2].x--;
232         } else {
233                 pt[0].x--;
234                 pt[2].x++;
235         }
236         XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
237 }
238
239 static Pixmap makePixmap(WScreen * scr, int width, int height, int side, Pixmap * mask)
240 {
241         WBalloon *bal = scr->balloon;
242         Pixmap bitmap;
243         Pixmap pixmap;
244         int x, y;
245
246         bitmap = XCreatePixmap(dpy, scr->root_win, width + SPACE, height + SPACE, 1);
247
248         if (!bal->monoGC) {
249                 bal->monoGC = XCreateGC(dpy, bitmap, 0, NULL);
250         }
251         XSetForeground(dpy, bal->monoGC, 0);
252         XFillRectangle(dpy, bitmap, bal->monoGC, 0, 0, width + SPACE, height + SPACE);
253
254         pixmap = XCreatePixmap(dpy, scr->root_win, width + SPACE, height + SPACE, scr->w_depth);
255         XSetForeground(dpy, scr->draw_gc, scr->black_pixel);
256         XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width + SPACE, height + SPACE);
257
258         if (side & BOTTOM) {
259                 y = 0;
260         } else {
261                 y = SPACE;
262         }
263         x = 0;
264
265         drawBalloon(scr, bitmap, pixmap, x, y, width, height, side);
266
267         *mask = bitmap;
268
269         return pixmap;
270 }
271
272 static void showText(WScreen * scr, int x, int y, int h, int w, char *text)
273 {
274         int width;
275         int height;
276         Pixmap pixmap;
277         Pixmap mask;
278         WMFont *font = scr->info_text_font;
279         int side = 0;
280         int ty;
281         int bx, by;
282
283         if (scr->balloon->contents)
284                 XFreePixmap(dpy, scr->balloon->contents);
285
286         width = getMaxStringWidth(font, text) + 16;
287         height = countLines(text) * WMFontHeight(font) + 4;
288
289         if (height < 16)
290                 height = 16;
291         if (width < height)
292                 width = height;
293
294         if (x + width > scr->scr_width) {
295                 side = RIGHT;
296                 bx = x - width + w / 2;
297                 if (bx < 0)
298                         bx = 0;
299         } else {
300                 side = LEFT;
301                 bx = x + w / 2;
302         }
303         if (bx + width > scr->scr_width)
304                 bx = scr->scr_width - width;
305
306         if (y - (height + SPACE) < 0) {
307                 side |= TOP;
308                 by = y + h - 1;
309                 ty = SPACE;
310         } else {
311                 side |= BOTTOM;
312                 by = y - (height + SPACE);
313                 ty = 0;
314         }
315         pixmap = makePixmap(scr, width, height, side, &mask);
316
317         drawMultiLineString(scr->wmscreen, pixmap, scr->black, font, 8, ty + 2, text, strlen(text));
318
319         XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
320         scr->balloon->contents = pixmap;
321
322         XResizeWindow(dpy, scr->balloon->window, width, height + SPACE);
323         XShapeCombineMask(dpy, scr->balloon->window, ShapeBounding, 0, 0, mask, ShapeSet);
324         XFreePixmap(dpy, mask);
325         XMoveWindow(dpy, scr->balloon->window, bx, by);
326         XMapRaised(dpy, scr->balloon->window);
327
328         scr->balloon->mapped = 1;
329 }
330 #else                           /* !SHAPED_BALLOON */
331
332 static void showText(WScreen * scr, int x, int y, int h, int w, char *text)
333 {
334         int width;
335         int height;
336         Pixmap pixmap;
337         WMFont *font = scr->info_text_font;
338
339         if (scr->balloon->contents)
340                 XFreePixmap(dpy, scr->balloon->contents);
341
342         width = getMaxStringWidth(font, text) + 8;
343         /*width = WMWidthOfString(font, text, strlen(text))+8; */
344         height = countLines(text) * WMFontHeight(font) + 4;
345
346         if (x < 0)
347                 x = 0;
348         else if (x + width > scr->scr_width - 1)
349                 x = scr->scr_width - width;
350
351         if (y - height - 2 < 0) {
352                 y += h;
353                 if (y < 0)
354                         y = 0;
355         } else {
356                 y -= height + 2;
357         }
358
359         if (scr->window_title_texture[0])
360                 XSetForeground(dpy, scr->draw_gc, scr->window_title_texture[0]->any.color.pixel);
361         else
362                 XSetForeground(dpy, scr->draw_gc, scr->light_pixel);
363
364         pixmap = XCreatePixmap(dpy, scr->root_win, width, height, scr->w_depth);
365         XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width, height);
366
367         drawMultiLineString(scr->wmscreen, pixmap, scr->window_title_color[0], font, 4, 2, text, strlen(text));
368
369         XResizeWindow(dpy, scr->balloon->window, width, height);
370         XMoveWindow(dpy, scr->balloon->window, x, y);
371
372         XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
373         XClearWindow(dpy, scr->balloon->window);
374         XMapRaised(dpy, scr->balloon->window);
375
376         scr->balloon->contents = pixmap;
377
378         scr->balloon->mapped = 1;
379 }
380 #endif                          /* !SHAPED_BALLOON */
381
382 static void showBalloon(WScreen * scr)
383 {
384         int x, y;
385         Window foow;
386         unsigned foo, w;
387
388         if (scr->balloon) {
389                 scr->balloon->timer = NULL;
390                 scr->balloon->ignoreTimer = 1;
391         }
392
393         if (!XGetGeometry(dpy, scr->balloon->objectWindow, &foow, &x, &y, &w, &foo, &foo, &foo)) {
394                 scr->balloon->prevType = 0;
395                 return;
396         }
397         showText(scr, x, y, scr->balloon->h, w, scr->balloon->text);
398 }
399
400 static void frameBalloon(WObjDescriptor * object)
401 {
402         WFrameWindow *fwin = (WFrameWindow *) object->parent;
403         WScreen *scr = fwin->core->screen_ptr;
404
405         if (fwin->titlebar != object->self || !fwin->flags.is_client_window_frame) {
406                 wBalloonHide(scr);
407                 return;
408         }
409         if (fwin->title && fwin->flags.incomplete_title) {
410                 scr->balloon->h = (fwin->titlebar ? fwin->titlebar->height : 0);
411                 scr->balloon->text = wstrdup(fwin->title);
412                 scr->balloon->objectWindow = fwin->core->window;
413                 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
414         }
415 }
416
417 static void miniwindowBalloon(WObjDescriptor * object)
418 {
419         WIcon *icon = (WIcon *) object->parent;
420         WScreen *scr = icon->core->screen_ptr;
421
422         if (!icon->icon_name) {
423                 wBalloonHide(scr);
424                 return;
425         }
426         scr->balloon->h = icon->core->height;
427         scr->balloon->text = wstrdup(icon->icon_name);
428         scr->balloon->objectWindow = icon->core->window;
429         if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_APPICON)
430             && scr->balloon->ignoreTimer) {
431                 XUnmapWindow(dpy, scr->balloon->window);
432                 showBalloon(scr);
433         } else {
434                 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
435         }
436 }
437
438 static void appiconBalloon(WObjDescriptor * object)
439 {
440         WAppIcon *aicon = (WAppIcon *) object->parent;
441         WScreen *scr = aicon->icon->core->screen_ptr;
442         char *tmp;
443
444         if (aicon->command && aicon->wm_class) {
445                 int len = strlen(aicon->command) + strlen(aicon->wm_class) + 8;
446                 tmp = wmalloc(len);
447                 snprintf(tmp, len, "%s\n(%s)", aicon->wm_class, aicon->command);
448                 scr->balloon->text = tmp;
449         } else if (aicon->command) {
450                 scr->balloon->text = wstrdup(aicon->command);
451         } else if (aicon->wm_class) {
452                 scr->balloon->text = wstrdup(aicon->wm_class);
453         } else {
454                 wBalloonHide(scr);
455                 return;
456         }
457         scr->balloon->h = aicon->icon->core->height - 2;
458
459         scr->balloon->objectWindow = aicon->icon->core->window;
460         if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_MINIWINDOW)
461             && scr->balloon->ignoreTimer) {
462                 XUnmapWindow(dpy, scr->balloon->window);
463                 showBalloon(scr);
464         } else {
465                 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
466         }
467 }
468
469 void wBalloonInitialize(WScreen * scr)
470 {
471         WBalloon *bal;
472         XSetWindowAttributes attribs;
473         unsigned long vmask;
474
475         bal = wmalloc(sizeof(WBalloon));
476         memset(bal, 0, sizeof(WBalloon));
477
478         scr->balloon = bal;
479
480         vmask = CWSaveUnder | CWOverrideRedirect | CWColormap | CWBackPixel | CWBorderPixel;
481         attribs.save_under = True;
482         attribs.override_redirect = True;
483         attribs.colormap = scr->w_colormap;
484         attribs.background_pixel = scr->icon_back_texture->normal.pixel;
485         attribs.border_pixel = 0;       /* do not care */
486
487         bal->window = XCreateWindow(dpy, scr->root_win, 1, 1, 10, 10, 1,
488                                     scr->w_depth, CopyFromParent, scr->w_visual, vmask, &attribs);
489 #if 0
490         /* select EnterNotify to so that the balloon will be unmapped
491          * when the pointer is moved over it */
492         XSelectInput(dpy, bal->window, EnterWindowMask);
493 #endif
494 }
495
496 void wBalloonEnteredObject(WScreen * scr, WObjDescriptor * object)
497 {
498         WBalloon *balloon = scr->balloon;
499
500         if (balloon->timer) {
501                 WMDeleteTimerHandler(balloon->timer);
502                 balloon->timer = NULL;
503                 balloon->ignoreTimer = 0;
504         }
505
506         if (scr->balloon->text)
507                 wfree(scr->balloon->text);
508         scr->balloon->text = NULL;
509
510         if (!object) {
511                 wBalloonHide(scr);
512                 balloon->ignoreTimer = 0;
513                 return;
514         }
515         switch (object->parent_type) {
516         case WCLASS_FRAME:
517                 if (wPreferences.window_balloon) {
518                         frameBalloon(object);
519                 }
520                 break;
521
522         case WCLASS_DOCK_ICON:
523                 if (object->parent != scr->clip_icon && wPreferences.appicon_balloon)
524                         appiconBalloon(object);
525                 else
526                         wBalloonHide(scr);
527                 break;
528
529         case WCLASS_MINIWINDOW:
530                 if (wPreferences.miniwin_balloon) {
531                         miniwindowBalloon(object);
532                 }
533                 break;
534         case WCLASS_APPICON:
535                 if (wPreferences.appicon_balloon)
536                         appiconBalloon(object);
537                 break;
538         default:
539                 wBalloonHide(scr);
540                 break;
541         }
542         scr->balloon->prevType = object->parent_type;
543 }
544
545 void wBalloonHide(WScreen * scr)
546 {
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;
554                 }
555                 scr->balloon->prevType = 0;
556         }
557 }
558
559 #endif