shortcutWindows moved to w_global
[wmaker-crm.git] / src / balloon.c
blob6c056e4485d8540db5f3849dfcc619d6501bd5ef
1 /*
2 * Window Maker window manager
4 * Copyright (c) 1998-2003 Alfredo K. Kojima
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 along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "wconfig.h"
23 #ifdef BALLOON_TEXT
25 #include <stdio.h>
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 "WindowMaker.h"
38 #include "screen.h"
39 #include "texture.h"
40 #include "framewin.h"
41 #include "icon.h"
42 #include "appicon.h"
43 #include "workspace.h"
44 #include "balloon.h"
47 typedef struct _WBalloon {
48 Window window;
50 #ifdef SHAPED_BALLOON
51 GC monoGC;
52 #endif
53 int prevType;
55 Window objectWindow;
56 char *text;
57 int h;
59 WMHandlerID timer;
61 Pixmap contents;
63 char mapped;
64 char ignoreTimer;
65 } WBalloon;
67 #define TOP 0
68 #define BOTTOM 1
69 #define LEFT 0
70 #define RIGHT 2
72 #define TLEFT (TOP|LEFT)
73 #define TRIGHT (TOP|RIGHT)
74 #define BLEFT (BOTTOM|LEFT)
75 #define BRIGHT (BOTTOM|RIGHT)
77 static int countLines(const char *text)
79 const char *p = text;
80 int h = 1;
82 while (*p) {
83 if (*p == '\n' && p[1] != 0)
84 h++;
85 p++;
87 return h;
90 static int getMaxStringWidth(WMFont * font, const char *text)
92 const char *p = text;
93 const char *pb = p;
94 int pos = 0;
95 int w = 0, wt;
97 while (*p) {
98 if (*p == '\n') {
99 wt = WMWidthOfString(font, pb, pos);
100 if (wt > w)
101 w = wt;
102 pos = 0;
103 pb = p + 1;
104 } else {
105 pos++;
107 p++;
109 if (pos > 0) {
110 wt = WMWidthOfString(font, pb, pos);
111 if (wt > w)
112 w = wt;
114 return w;
117 static void
118 drawMultiLineString(WMScreen * scr, Pixmap pixmap, WMColor * color,
119 WMFont *font, int x, int y, const char *text, int len)
121 const char *p = text;
122 const char *pb = p;
123 int l = 0, pos = 0;
124 int height = WMFontHeight(font);
126 while (*p && p - text < len) {
127 if (*p == '\n') {
128 WMDrawString(scr, pixmap, color, font, x, y + l * height, pb, pos);
129 l++;
130 pos = 0;
131 pb = p + 1;
132 } else {
133 pos++;
135 p++;
137 if (pos > 0) {
138 WMDrawString(scr, pixmap, color, font, x, y + l * height, pb, pos);
142 #ifdef SHAPED_BALLOON
144 #define SPACE 12
146 static void drawBalloon(WScreen * scr, Pixmap bitmap, Pixmap pix, int x, int y, int w, int h, int side)
148 GC bgc = scr->balloon->monoGC;
149 GC gc = scr->draw_gc;
150 int rad = h * 3 / 10;
151 XPoint pt[3], ipt[3];
152 int w1;
154 /* outline */
155 XSetForeground(dpy, bgc, 1);
157 XFillArc(dpy, bitmap, bgc, x, y, rad, rad, 90 * 64, 90 * 64);
158 XFillArc(dpy, bitmap, bgc, x, y + h - 1 - rad, rad, rad, 180 * 64, 90 * 64);
160 XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y, rad, rad, 0 * 64, 90 * 64);
161 XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y + h - 1 - rad, rad, rad, 270 * 64, 90 * 64);
163 XFillRectangle(dpy, bitmap, bgc, x, y + rad / 2, w, h - rad);
164 XFillRectangle(dpy, bitmap, bgc, x + rad / 2, y, w - rad, h);
166 /* interior */
167 XSetForeground(dpy, gc, scr->white_pixel);
169 XFillArc(dpy, pix, gc, x + 1, y + 1, rad, rad, 90 * 64, 90 * 64);
170 XFillArc(dpy, pix, gc, x + 1, y + h - 2 - rad, rad, rad, 180 * 64, 90 * 64);
172 XFillArc(dpy, pix, gc, x + w - 2 - rad, y + 1, rad, rad, 0 * 64, 90 * 64);
173 XFillArc(dpy, pix, gc, x + w - 2 - rad, y + h - 2 - rad, rad, rad, 270 * 64, 90 * 64);
175 XFillRectangle(dpy, pix, gc, x + 1, y + 1 + rad / 2, w - 2, h - 2 - rad);
176 XFillRectangle(dpy, pix, gc, x + 1 + rad / 2, y + 1, w - 2 - rad, h - 2);
178 if (side & BOTTOM) {
179 pt[0].y = y + h - 1;
180 pt[1].y = y + h - 1 + SPACE;
181 pt[2].y = y + h - 1;
182 ipt[0].y = pt[0].y - 1;
183 ipt[1].y = pt[1].y - 1;
184 ipt[2].y = pt[2].y - 1;
185 } else {
186 pt[0].y = y;
187 pt[1].y = y - SPACE;
188 pt[2].y = y;
189 ipt[0].y = pt[0].y + 1;
190 ipt[1].y = pt[1].y + 1;
191 ipt[2].y = pt[2].y + 1;
194 /*w1 = WMAX(h, 24); */
195 w1 = WMAX(h, 21);
197 if (side & RIGHT) {
198 pt[0].x = x + w - w1 + 2 * w1 / 16;
199 pt[1].x = x + w - w1 + 11 * w1 / 16;
200 pt[2].x = x + w - w1 + 7 * w1 / 16;
201 ipt[0].x = x + 1 + w - w1 + 2 * (w1 - 1) / 16;
202 ipt[1].x = x + 1 + w - w1 + 11 * (w1 - 1) / 16;
203 ipt[2].x = x + 1 + w - w1 + 7 * (w1 - 1) / 16;
204 /*ipt[0].x = pt[0].x+1;
205 ipt[1].x = pt[1].x;
206 ipt[2].x = pt[2].x; */
207 } else {
208 pt[0].x = x + w1 - 2 * w1 / 16;
209 pt[1].x = x + w1 - 11 * w1 / 16;
210 pt[2].x = x + w1 - 7 * w1 / 16;
211 ipt[0].x = x - 1 + w1 - 2 * (w1 - 1) / 16;
212 ipt[1].x = x - 1 + w1 - 11 * (w1 - 1) / 16;
213 ipt[2].x = x - 1 + w1 - 7 * (w1 - 1) / 16;
214 /*ipt[0].x = pt[0].x-1;
215 ipt[1].x = pt[1].x;
216 ipt[2].x = pt[2].x; */
219 XFillPolygon(dpy, bitmap, bgc, pt, 3, Convex, CoordModeOrigin);
220 XFillPolygon(dpy, pix, gc, ipt, 3, Convex, CoordModeOrigin);
222 /* fix outline */
223 XSetForeground(dpy, gc, scr->black_pixel);
225 XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
226 if (side & RIGHT) {
227 pt[0].x++;
228 pt[2].x--;
229 } else {
230 pt[0].x--;
231 pt[2].x++;
233 XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
236 static Pixmap makePixmap(WScreen * scr, int width, int height, int side, Pixmap * mask)
238 WBalloon *bal = scr->balloon;
239 Pixmap bitmap;
240 Pixmap pixmap;
241 int x, y;
243 bitmap = XCreatePixmap(dpy, scr->root_win, width + SPACE, height + SPACE, 1);
245 if (!bal->monoGC) {
246 bal->monoGC = XCreateGC(dpy, bitmap, 0, NULL);
248 XSetForeground(dpy, bal->monoGC, 0);
249 XFillRectangle(dpy, bitmap, bal->monoGC, 0, 0, width + SPACE, height + SPACE);
251 pixmap = XCreatePixmap(dpy, scr->root_win, width + SPACE, height + SPACE, scr->w_depth);
252 XSetForeground(dpy, scr->draw_gc, scr->black_pixel);
253 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width + SPACE, height + SPACE);
255 if (side & BOTTOM) {
256 y = 0;
257 } else {
258 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 showBalloon(WScreen * scr)
381 int x, y;
382 Window foow;
383 unsigned foo, w;
385 if (scr->balloon) {
386 scr->balloon->timer = NULL;
387 scr->balloon->ignoreTimer = 1;
390 if (!XGetGeometry(dpy, scr->balloon->objectWindow, &foow, &x, &y, &w, &foo, &foo, &foo)) {
391 scr->balloon->prevType = 0;
392 return;
394 showText(scr, x, y, scr->balloon->h, w, scr->balloon->text);
397 static void frameBalloon(WObjDescriptor * object)
399 WFrameWindow *fwin = (WFrameWindow *) object->parent;
400 WScreen *scr = fwin->core->screen_ptr;
402 if (fwin->titlebar != object->self || !fwin->flags.is_client_window_frame) {
403 wBalloonHide(scr);
404 return;
406 if (fwin->title && fwin->flags.incomplete_title) {
407 scr->balloon->h = (fwin->titlebar ? fwin->titlebar->height : 0);
408 scr->balloon->text = wstrdup(fwin->title);
409 scr->balloon->objectWindow = fwin->core->window;
410 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
414 static void miniwindowBalloon(WObjDescriptor * object)
416 WIcon *icon = (WIcon *) object->parent;
417 WScreen *scr = icon->core->screen_ptr;
419 if (!icon->icon_name) {
420 wBalloonHide(scr);
421 return;
423 scr->balloon->h = icon->core->height;
424 scr->balloon->text = wstrdup(icon->icon_name);
425 scr->balloon->objectWindow = icon->core->window;
426 if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_APPICON)
427 && scr->balloon->ignoreTimer) {
428 XUnmapWindow(dpy, scr->balloon->window);
429 showBalloon(scr);
430 } else {
431 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
435 static void appiconBalloon(WObjDescriptor *object)
437 WAppIcon *aicon = (WAppIcon *) object->parent;
438 WScreen *scr = aicon->icon->core->screen_ptr;
439 char *tmp;
441 /* Show balloon if it is the Clip and the workspace name is > 5 chars */
442 if (object->parent == w_global.clip.icon) {
443 if (strlen(w_global.workspace.array[w_global.workspace.current]->name) > 5) {
444 scr->balloon->text = wstrdup(w_global.workspace.array[w_global.workspace.current]->name);
445 } else {
446 wBalloonHide(scr);
447 return;
449 } else if (aicon->command && aicon->wm_class) {
450 int len;
451 /* Check to see if it is a GNUstep app */
452 if (strcmp(aicon->wm_class, "GNUstep") == 0)
453 len = strlen(aicon->command) + strlen(aicon->wm_instance) + 8;
454 else
455 len = strlen(aicon->command) + strlen(aicon->wm_class) + 8;
456 tmp = wmalloc(len);
457 /* Check to see if it is a GNUstep App */
458 if (strcmp(aicon->wm_class, "GNUstep") == 0)
459 snprintf(tmp, len, "%s\n(%s)", aicon->wm_instance, aicon->command);
460 else
461 snprintf(tmp, len, "%s\n(%s)", aicon->wm_class, aicon->command);
462 scr->balloon->text = tmp;
463 } else if (aicon->command) {
464 scr->balloon->text = wstrdup(aicon->command);
465 } else if (aicon->wm_class) {
466 /* Check to see if it is a GNUstep App */
467 if (strcmp(aicon->wm_class, "GNUstep") == 0)
468 scr->balloon->text = wstrdup(aicon->wm_instance);
469 else
470 scr->balloon->text = wstrdup(aicon->wm_class);
471 } else {
472 wBalloonHide(scr);
473 return;
475 scr->balloon->h = aicon->icon->core->height - 2;
477 scr->balloon->objectWindow = aicon->icon->core->window;
478 if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_MINIWINDOW)
479 && scr->balloon->ignoreTimer) {
480 XUnmapWindow(dpy, scr->balloon->window);
481 showBalloon(scr);
482 } else {
483 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
487 void wBalloonInitialize(WScreen * scr)
489 WBalloon *bal;
490 XSetWindowAttributes attribs;
491 unsigned long vmask;
493 bal = wmalloc(sizeof(WBalloon));
495 scr->balloon = bal;
497 vmask = CWSaveUnder | CWOverrideRedirect | CWColormap | CWBackPixel | CWBorderPixel;
498 attribs.save_under = True;
499 attribs.override_redirect = True;
500 attribs.colormap = scr->w_colormap;
501 attribs.background_pixel = scr->icon_back_texture->normal.pixel;
502 attribs.border_pixel = 0; /* do not care */
504 bal->window = XCreateWindow(dpy, scr->root_win, 1, 1, 10, 10, 1,
505 scr->w_depth, CopyFromParent, scr->w_visual, vmask, &attribs);
506 #if 0
507 /* select EnterNotify to so that the balloon will be unmapped
508 * when the pointer is moved over it */
509 XSelectInput(dpy, bal->window, EnterWindowMask);
510 #endif
513 void wBalloonEnteredObject(WScreen * scr, WObjDescriptor * object)
515 WBalloon *balloon = scr->balloon;
517 if (balloon->timer) {
518 WMDeleteTimerHandler(balloon->timer);
519 balloon->timer = NULL;
520 balloon->ignoreTimer = 0;
523 if (scr->balloon->text)
524 wfree(scr->balloon->text);
525 scr->balloon->text = NULL;
527 if (!object) {
528 wBalloonHide(scr);
529 balloon->ignoreTimer = 0;
530 return;
533 switch (object->parent_type) {
534 case WCLASS_FRAME:
535 if (wPreferences.window_balloon)
536 frameBalloon(object);
537 break;
538 case WCLASS_DOCK_ICON:
539 if (wPreferences.appicon_balloon)
540 appiconBalloon(object);
541 break;
542 case WCLASS_MINIWINDOW:
543 if (wPreferences.miniwin_balloon)
544 miniwindowBalloon(object);
545 break;
546 case WCLASS_APPICON:
547 if (wPreferences.appicon_balloon)
548 appiconBalloon(object);
549 break;
550 default:
551 wBalloonHide(scr);
552 break;
554 scr->balloon->prevType = object->parent_type;
557 void wBalloonHide(WScreen * scr)
559 if (scr) {
560 if (scr->balloon->mapped) {
561 XUnmapWindow(dpy, scr->balloon->window);
562 scr->balloon->mapped = 0;
563 } else if (scr->balloon->timer) {
564 WMDeleteTimerHandler(scr->balloon->timer);
565 scr->balloon->timer = NULL;
567 scr->balloon->prevType = 0;
571 #endif