Better outline when drawing balloons
[wmaker-crm.git] / src / balloon.c
blob3b9e1a0ef20ba3c1a024860f7a70b1f799585b37
1 /*
2 * Window Maker window manager
3 *
4 * Copyright (c) 1998-2002 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.
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
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.
22 #include "wconfig.h"
24 #ifdef BALLOON_TEXT
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 <WINGs/WINGsP.h>
39 #include "WindowMaker.h"
40 #include "screen.h"
41 #include "texture.h"
42 #include "wcore.h"
43 #include "framewin.h"
44 #include "icon.h"
45 #include "appicon.h"
46 #include "funcs.h"
47 #include "workspace.h"
48 #include "balloon.h"
52 extern WPreferences wPreferences;
54 typedef struct _WBalloon {
55 Window window;
57 #ifdef SHAPED_BALLOON
58 GC monoGC;
59 #endif
60 int prevType;
62 Window objectWindow;
63 char *text;
64 int h;
66 WMHandlerID timer;
68 Pixmap contents;
70 char mapped;
71 char ignoreTimer;
72 } WBalloon;
75 #define TOP 0
76 #define BOTTOM 1
77 #define LEFT 0
78 #define RIGHT 2
80 #define TLEFT (TOP|LEFT)
81 #define TRIGHT (TOP|RIGHT)
82 #define BLEFT (BOTTOM|LEFT)
83 #define BRIGHT (BOTTOM|RIGHT)
86 #ifdef SHAPED_BALLOON
88 #define SPACE 12
91 static void
92 drawBalloon(WScreen *scr, Pixmap bitmap, Pixmap pix, int x, int y, int w,
93 int h, int side)
95 GC bgc = scr->balloon->monoGC;
96 GC gc = scr->draw_gc;
97 int rad = h*3/10;
98 XPoint pt[3], ipt[3];
99 int w1;
101 /* outline */
102 XSetForeground(dpy, bgc, 1);
104 XFillArc(dpy, bitmap, bgc, x, y, rad, rad, 90*64, 90*64);
105 XFillArc(dpy, bitmap, bgc, x, y+h-1-rad, rad, rad, 180*64, 90*64);
107 XFillArc(dpy, bitmap, bgc, x+w-1-rad, y, rad, rad, 0*64, 90*64);
108 XFillArc(dpy, bitmap, bgc, x+w-1-rad, y+h-1-rad, rad, rad, 270*64, 90*64);
110 XFillRectangle(dpy, bitmap, bgc, x, y+rad/2, w, h-rad);
111 XFillRectangle(dpy, bitmap, bgc, x+rad/2, y, w-rad, h);
113 /* interior */
114 XSetForeground(dpy, gc, scr->white_pixel);
116 XFillArc(dpy, pix, gc, x+1, y+1, rad, rad, 90*64, 90*64);
117 XFillArc(dpy, pix, gc, x+1, y+h-2-rad, rad, rad, 180*64, 90*64);
119 XFillArc(dpy, pix, gc, x+w-2-rad, y+1, rad, rad, 0*64, 90*64);
120 XFillArc(dpy, pix, gc, x+w-2-rad, y+h-2-rad, rad, rad, 270*64, 90*64);
122 XFillRectangle(dpy, pix, gc, x+1, y+1+rad/2, w-2, h-2-rad);
123 XFillRectangle(dpy, pix, gc, x+1+rad/2, y+1, w-2-rad, h-2);
125 if (side & BOTTOM) {
126 pt[0].y = y+h-1;
127 pt[1].y = y+h-1+SPACE;
128 pt[2].y = y+h-1;
129 ipt[0].y = pt[0].y-1;
130 ipt[1].y = pt[1].y-1;
131 ipt[2].y = pt[2].y-1;
132 } else {
133 pt[0].y = y;
134 pt[1].y = y-SPACE;
135 pt[2].y = y;
136 ipt[0].y = pt[0].y+1;
137 ipt[1].y = pt[1].y+1;
138 ipt[2].y = pt[2].y+1;
141 /*w1 = WMAX(h, 24);*/
142 w1 = WMAX(h, 21);
144 if (side & RIGHT) {
145 pt[0].x = x+w-w1+2*w1/16;
146 pt[1].x = x+w-w1+11*w1/16;
147 pt[2].x = x+w-w1+7*w1/16;
148 ipt[0].x = x+1+w-w1+2*(w1-1)/16;
149 ipt[1].x = x+1+w-w1+11*(w1-1)/16;
150 ipt[2].x = x+1+w-w1+7*(w1-1)/16;
151 /*ipt[0].x = pt[0].x+1;
152 ipt[1].x = pt[1].x;
153 ipt[2].x = pt[2].x;*/
154 } else {
155 pt[0].x = x+w1-2*w1/16;
156 pt[1].x = x+w1-11*w1/16;
157 pt[2].x = x+w1-7*w1/16;
158 ipt[0].x = x-1+w1-2*(w1-1)/16;
159 ipt[1].x = x-1+w1-11*(w1-1)/16;
160 ipt[2].x = x-1+w1-7*(w1-1)/16;
161 /*ipt[0].x = pt[0].x-1;
162 ipt[1].x = pt[1].x;
163 ipt[2].x = pt[2].x;*/
166 XFillPolygon(dpy, bitmap, bgc, pt, 3, Convex, CoordModeOrigin);
167 XFillPolygon(dpy, pix, gc, ipt, 3, Convex, CoordModeOrigin);
169 /* fix outline */
170 XSetForeground(dpy, gc, scr->black_pixel);
172 XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
173 if (side & RIGHT) {
174 pt[0].x++;
175 pt[2].x--;
176 } else {
177 pt[0].x--;
178 pt[2].x++;
180 XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
184 static Pixmap
185 makePixmap(WScreen *scr, int width, int height, int side, Pixmap *mask)
187 WBalloon *bal = scr->balloon;
188 Pixmap bitmap;
189 Pixmap pixmap;
190 int x, y;
192 bitmap = XCreatePixmap(dpy, scr->root_win, width+SPACE, height+SPACE, 1);
194 if (!bal->monoGC) {
195 bal->monoGC = XCreateGC(dpy, bitmap, 0, NULL);
197 XSetForeground(dpy, bal->monoGC, 0);
198 XFillRectangle(dpy, bitmap, bal->monoGC, 0, 0, width+SPACE, height+SPACE);
200 pixmap = XCreatePixmap(dpy, scr->root_win, width+SPACE, height+SPACE,
201 scr->w_depth);
202 XSetForeground(dpy, scr->draw_gc, scr->black_pixel);
203 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width+SPACE, height+SPACE);
205 if (side & BOTTOM) {
206 y = 0;
207 } else {
208 y = SPACE;
210 x = 0;
212 drawBalloon(scr, bitmap, pixmap, x, y, width, height, side);
214 *mask = bitmap;
216 return pixmap;
220 static void
221 showText(WScreen *scr, int x, int y, int h, int w, char *text)
223 int width;
224 int height;
225 Pixmap pixmap;
226 Pixmap mask;
227 WMFont *font = scr->info_text_font;
228 int side = 0;
229 int ty;
230 int bx, by;
232 if (scr->balloon->contents)
233 XFreePixmap(dpy, scr->balloon->contents);
235 width = WMWidthOfString(font, text, strlen(text))+16;
236 height = WMFontHeight(font) + 4;
238 if (height < 16)
239 height = 16;
240 if (width < height)
241 width = height;
244 if (x + width > scr->scr_width) {
245 side = RIGHT;
246 bx = x - width + w/2;
247 if (bx < 0)
248 bx = 0;
249 } else {
250 side = LEFT;
251 bx = x + w/2;
253 if (bx + width > scr->scr_width)
254 bx = scr->scr_width - width;
256 if (y - (height + SPACE) < 0) {
257 side |= TOP;
258 by = y+h-1;
259 ty = SPACE;
260 } else {
261 side |= BOTTOM;
262 by = y - (height + SPACE);
263 ty = 0;
265 pixmap = makePixmap(scr, width, height, side, &mask);
267 WMDrawString(scr->wmscreen, pixmap, scr->black, font, 8,
268 ty + (height - WMFontHeight(font))/2,
269 text, strlen(text));
271 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
272 scr->balloon->contents = pixmap;
274 XResizeWindow(dpy, scr->balloon->window, width, height+SPACE);
275 XShapeCombineMask(dpy, scr->balloon->window, ShapeBounding, 0, 0, mask,
276 ShapeSet);
277 XFreePixmap(dpy, mask);
278 XMoveWindow(dpy, scr->balloon->window, bx, by);
279 XMapRaised(dpy, scr->balloon->window);
282 scr->balloon->mapped = 1;
284 #else /* !SHAPED_BALLOON */
285 static void
286 showText(WScreen *scr, int x, int y, int h, int w, char *text)
288 int width;
289 int height;
290 Pixmap pixmap;
291 WMFont *font = scr->info_text_font;
293 if (scr->balloon->contents)
294 XFreePixmap(dpy, scr->balloon->contents);
296 width = WMWidthOfString(font, text, strlen(text))+8;
297 height = WMFontHeight(font) + 4;
299 if (x < 0)
300 x = 0;
301 else if (x + width > scr->scr_width-1)
302 x = scr->scr_width - width;
304 if (y - height - 2 < 0) {
305 y += h;
306 if (y < 0)
307 y = 0;
308 } else {
309 y -= height + 2;
312 if (scr->window_title_texture[0])
313 XSetForeground(dpy, scr->draw_gc,
314 scr->window_title_texture[0]->any.color.pixel);
315 else
316 XSetForeground(dpy, scr->draw_gc, scr->light_pixel);
318 pixmap = XCreatePixmap(dpy, scr->root_win, width, height, scr->w_depth);
319 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width, height);
321 WMDrawString(scr->wmscreen, pixmap, scr->window_title_color[0], font, 4, 2,
322 text, strlen(text));
324 XResizeWindow(dpy, scr->balloon->window, width, height);
325 XMoveWindow(dpy, scr->balloon->window, x, y);
327 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
328 XClearWindow(dpy, scr->balloon->window);
329 XMapRaised(dpy, scr->balloon->window);
331 scr->balloon->contents = pixmap;
333 scr->balloon->mapped = 1;
335 #endif /* !SHAPED_BALLOON */
338 static void
339 showBalloon(WScreen *scr)
341 int x, y;
342 Window foow;
343 unsigned foo, w;
345 if (scr->balloon) {
346 scr->balloon->timer = NULL;
347 scr->balloon->ignoreTimer = 1;
350 if (!XGetGeometry(dpy, scr->balloon->objectWindow, &foow, &x, &y,
351 &w, &foo, &foo, &foo)) {
352 scr->balloon->prevType = 0;
353 return;
355 showText(scr, x, y, scr->balloon->h, w, scr->balloon->text);
360 static void
361 frameBalloon(WObjDescriptor *object)
363 WFrameWindow *fwin = (WFrameWindow*)object->parent;
364 WScreen *scr = fwin->core->screen_ptr;
366 if (fwin->titlebar != object->self
367 || !fwin->flags.is_client_window_frame) {
368 wBalloonHide(scr);
369 return;
371 if (fwin->title && fwin->flags.incomplete_title) {
372 scr->balloon->h = (fwin->titlebar ? fwin->titlebar->height : 0);
373 scr->balloon->text = wstrdup(fwin->title);
374 scr->balloon->objectWindow = fwin->core->window;
375 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY,
376 (WMCallback*)showBalloon, scr);
381 static void
382 miniwindowBalloon(WObjDescriptor *object)
384 WIcon *icon = (WIcon*)object->parent;
385 WScreen *scr = icon->core->screen_ptr;
387 if (!icon->icon_name) {
388 wBalloonHide(scr);
389 return;
391 scr->balloon->h = icon->core->height;
392 scr->balloon->text = wstrdup(icon->icon_name);
393 scr->balloon->objectWindow = icon->core->window;
394 if ((scr->balloon->prevType == object->parent_type
395 || scr->balloon->prevType == WCLASS_APPICON)
396 && scr->balloon->ignoreTimer) {
397 XUnmapWindow(dpy, scr->balloon->window);
398 showBalloon(scr);
399 } else {
400 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY,
401 (WMCallback*)showBalloon, scr);
407 static void
408 appiconBalloon(WObjDescriptor *object)
410 WAppIcon *aicon = (WAppIcon*)object->parent;
411 WScreen *scr = aicon->icon->core->screen_ptr;
412 char *tmp;
414 if (aicon->command && aicon->wm_class) {
415 int len = strlen(aicon->command)+strlen(aicon->wm_class)+8;
416 tmp = wmalloc(len);
417 snprintf(tmp, len, "%s (%s)", aicon->wm_class, aicon->command);
418 scr->balloon->text = tmp;
419 } else if (aicon->command) {
420 scr->balloon->text = wstrdup(aicon->command);
421 } else if (aicon->wm_class) {
422 scr->balloon->text = wstrdup(aicon->wm_class);
423 } else {
424 wBalloonHide(scr);
425 return;
427 scr->balloon->h = aicon->icon->core->height-2;
429 scr->balloon->objectWindow = aicon->icon->core->window;
430 if ((scr->balloon->prevType == object->parent_type
431 || scr->balloon->prevType == WCLASS_MINIWINDOW)
432 && scr->balloon->ignoreTimer) {
433 XUnmapWindow(dpy, scr->balloon->window);
434 showBalloon(scr);
435 } else {
436 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY,
437 (WMCallback*)showBalloon, scr);
443 void
444 wBalloonInitialize(WScreen *scr)
446 WBalloon *bal;
447 XSetWindowAttributes attribs;
448 unsigned long vmask;
450 bal = wmalloc(sizeof(WBalloon));
451 memset(bal, 0, sizeof(WBalloon));
453 scr->balloon = bal;
455 vmask = CWSaveUnder|CWOverrideRedirect|CWColormap|CWBackPixel
456 |CWBorderPixel;
457 attribs.save_under = True;
458 attribs.override_redirect = True;
459 attribs.colormap = scr->w_colormap;
460 attribs.background_pixel = scr->icon_back_texture->normal.pixel;
461 attribs.border_pixel = 0; /* do not care */
463 bal->window = XCreateWindow(dpy, scr->root_win, 1, 1, 10, 10, 1,
464 scr->w_depth, CopyFromParent,
465 scr->w_visual, vmask, &attribs);
466 #if 0
467 /* select EnterNotify to so that the balloon will be unmapped
468 * when the pointer is moved over it */
469 XSelectInput(dpy, bal->window, EnterWindowMask);
470 #endif
475 void
476 wBalloonEnteredObject(WScreen *scr, WObjDescriptor *object)
478 WBalloon *balloon = scr->balloon;
480 if (balloon->timer) {
481 WMDeleteTimerHandler(balloon->timer);
482 balloon->timer = NULL;
483 balloon->ignoreTimer = 0;
486 if (scr->balloon->text)
487 wfree(scr->balloon->text);
488 scr->balloon->text = NULL;
490 if (!object) {
491 wBalloonHide(scr);
492 balloon->ignoreTimer = 0;
493 return;
495 switch (object->parent_type) {
496 case WCLASS_FRAME:
497 if (wPreferences.window_balloon) {
498 frameBalloon(object);
500 break;
502 case WCLASS_DOCK_ICON:
503 if (object->parent != scr->clip_icon && wPreferences.appicon_balloon)
504 appiconBalloon(object);
505 else
506 wBalloonHide(scr);
507 break;
509 case WCLASS_MINIWINDOW:
510 if (wPreferences.miniwin_balloon) {
511 miniwindowBalloon(object);
513 break;
514 case WCLASS_APPICON:
515 if (wPreferences.appicon_balloon)
516 appiconBalloon(object);
517 break;
518 default:
519 wBalloonHide(scr);
520 break;
522 scr->balloon->prevType = object->parent_type;
527 void
528 wBalloonHide(WScreen *scr)
530 if (scr) {
531 if (scr->balloon->mapped) {
532 XUnmapWindow(dpy, scr->balloon->window);
533 scr->balloon->mapped = 0;
534 } else if (scr->balloon->timer) {
535 WMDeleteTimerHandler(scr->balloon->timer);
536 scr->balloon->timer = NULL;
538 scr->balloon->prevType = 0;
541 #endif