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