Make WPrefs' Expert panel scrollable
[wmaker-crm.git] / src / balloon.c
blobd046afa12561630b093a672cf16d4c8a93a005b4
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
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 "WindowMaker.h"
38 #include "screen.h"
39 #include "texture.h"
40 #include "framewin.h"
41 #include "icon.h"
42 #include "appicon.h"
43 #include "funcs.h"
44 #include "workspace.h"
45 #include "balloon.h"
47 extern WPreferences wPreferences;
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;
65 char mapped;
66 char ignoreTimer;
67 } WBalloon;
69 #define TOP 0
70 #define BOTTOM 1
71 #define LEFT 0
72 #define RIGHT 2
74 #define TLEFT (TOP|LEFT)
75 #define TRIGHT (TOP|RIGHT)
76 #define BLEFT (BOTTOM|LEFT)
77 #define BRIGHT (BOTTOM|RIGHT)
79 static int countLines(const char *text)
81 const char *p = text;
82 int h = 1;
84 while (*p) {
85 if (*p == '\n' && p[1] != 0)
86 h++;
87 p++;
89 return h;
92 static int getMaxStringWidth(WMFont * font, char *text)
94 char *p = text;
95 char *pb = p;
96 int pos = 0;
97 int w = 0, wt;
99 while (*p) {
100 if (*p == '\n') {
101 wt = WMWidthOfString(font, pb, pos);
102 if (wt > w)
103 w = wt;
104 pos = 0;
105 pb = p + 1;
106 } else {
107 pos++;
109 p++;
111 if (pos > 0) {
112 wt = WMWidthOfString(font, pb, pos);
113 if (wt > w)
114 w = wt;
116 return w;
119 static void
120 drawMultiLineString(WMScreen * scr, Pixmap pixmap, WMColor * color,
121 WMFont * font, int x, int y, char *text, int len)
123 char *p = text;
124 char *pb = p;
125 int l = 0, pos = 0;
126 int height = WMFontHeight(font);
128 while (*p && p - text < len) {
129 if (*p == '\n') {
130 WMDrawString(scr, pixmap, color, font, x, y + l * height, pb, pos);
131 l++;
132 pos = 0;
133 pb = p + 1;
134 } else {
135 pos++;
137 p++;
139 if (pos > 0) {
140 WMDrawString(scr, pixmap, color, font, x, y + l * height, pb, pos);
144 #ifdef SHAPED_BALLOON
146 #define SPACE 12
148 static void drawBalloon(WScreen * scr, Pixmap bitmap, Pixmap pix, int x, int y, int w, int h, int side)
150 GC bgc = scr->balloon->monoGC;
151 GC gc = scr->draw_gc;
152 int rad = h * 3 / 10;
153 XPoint pt[3], ipt[3];
154 int w1;
156 /* outline */
157 XSetForeground(dpy, bgc, 1);
159 XFillArc(dpy, bitmap, bgc, x, y, rad, rad, 90 * 64, 90 * 64);
160 XFillArc(dpy, bitmap, bgc, x, y + h - 1 - rad, rad, rad, 180 * 64, 90 * 64);
162 XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y, rad, rad, 0 * 64, 90 * 64);
163 XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y + h - 1 - rad, rad, rad, 270 * 64, 90 * 64);
165 XFillRectangle(dpy, bitmap, bgc, x, y + rad / 2, w, h - rad);
166 XFillRectangle(dpy, bitmap, bgc, x + rad / 2, y, w - rad, h);
168 /* interior */
169 XSetForeground(dpy, gc, scr->white_pixel);
171 XFillArc(dpy, pix, gc, x + 1, y + 1, rad, rad, 90 * 64, 90 * 64);
172 XFillArc(dpy, pix, gc, x + 1, y + h - 2 - rad, rad, rad, 180 * 64, 90 * 64);
174 XFillArc(dpy, pix, gc, x + w - 2 - rad, y + 1, rad, rad, 0 * 64, 90 * 64);
175 XFillArc(dpy, pix, gc, x + w - 2 - rad, y + h - 2 - rad, rad, rad, 270 * 64, 90 * 64);
177 XFillRectangle(dpy, pix, gc, x + 1, y + 1 + rad / 2, w - 2, h - 2 - rad);
178 XFillRectangle(dpy, pix, gc, x + 1 + rad / 2, y + 1, w - 2 - rad, h - 2);
180 if (side & BOTTOM) {
181 pt[0].y = y + h - 1;
182 pt[1].y = y + h - 1 + SPACE;
183 pt[2].y = y + h - 1;
184 ipt[0].y = pt[0].y - 1;
185 ipt[1].y = pt[1].y - 1;
186 ipt[2].y = pt[2].y - 1;
187 } else {
188 pt[0].y = y;
189 pt[1].y = y - SPACE;
190 pt[2].y = y;
191 ipt[0].y = pt[0].y + 1;
192 ipt[1].y = pt[1].y + 1;
193 ipt[2].y = pt[2].y + 1;
196 /*w1 = WMAX(h, 24); */
197 w1 = WMAX(h, 21);
199 if (side & RIGHT) {
200 pt[0].x = x + w - w1 + 2 * w1 / 16;
201 pt[1].x = x + w - w1 + 11 * w1 / 16;
202 pt[2].x = x + w - w1 + 7 * w1 / 16;
203 ipt[0].x = x + 1 + w - w1 + 2 * (w1 - 1) / 16;
204 ipt[1].x = x + 1 + w - w1 + 11 * (w1 - 1) / 16;
205 ipt[2].x = x + 1 + w - w1 + 7 * (w1 - 1) / 16;
206 /*ipt[0].x = pt[0].x+1;
207 ipt[1].x = pt[1].x;
208 ipt[2].x = pt[2].x; */
209 } else {
210 pt[0].x = x + w1 - 2 * w1 / 16;
211 pt[1].x = x + w1 - 11 * w1 / 16;
212 pt[2].x = x + w1 - 7 * w1 / 16;
213 ipt[0].x = x - 1 + w1 - 2 * (w1 - 1) / 16;
214 ipt[1].x = x - 1 + w1 - 11 * (w1 - 1) / 16;
215 ipt[2].x = x - 1 + w1 - 7 * (w1 - 1) / 16;
216 /*ipt[0].x = pt[0].x-1;
217 ipt[1].x = pt[1].x;
218 ipt[2].x = pt[2].x; */
221 XFillPolygon(dpy, bitmap, bgc, pt, 3, Convex, CoordModeOrigin);
222 XFillPolygon(dpy, pix, gc, ipt, 3, Convex, CoordModeOrigin);
224 /* fix outline */
225 XSetForeground(dpy, gc, scr->black_pixel);
227 XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
228 if (side & RIGHT) {
229 pt[0].x++;
230 pt[2].x--;
231 } else {
232 pt[0].x--;
233 pt[2].x++;
235 XDrawLines(dpy, pix, gc, pt, 3, CoordModeOrigin);
238 static Pixmap makePixmap(WScreen * scr, int width, int height, int side, Pixmap * mask)
240 WBalloon *bal = scr->balloon;
241 Pixmap bitmap;
242 Pixmap pixmap;
243 int x, y;
245 bitmap = XCreatePixmap(dpy, scr->root_win, width + SPACE, height + SPACE, 1);
247 if (!bal->monoGC) {
248 bal->monoGC = XCreateGC(dpy, bitmap, 0, NULL);
250 XSetForeground(dpy, bal->monoGC, 0);
251 XFillRectangle(dpy, bitmap, bal->monoGC, 0, 0, width + SPACE, height + SPACE);
253 pixmap = XCreatePixmap(dpy, scr->root_win, width + SPACE, height + SPACE, scr->w_depth);
254 XSetForeground(dpy, scr->draw_gc, scr->black_pixel);
255 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width + SPACE, height + SPACE);
257 if (side & BOTTOM) {
258 y = 0;
259 } else {
260 y = SPACE;
262 x = 0;
264 drawBalloon(scr, bitmap, pixmap, x, y, width, height, side);
266 *mask = bitmap;
268 return pixmap;
271 static void showText(WScreen * scr, int x, int y, int h, int w, char *text)
273 int width;
274 int height;
275 Pixmap pixmap;
276 Pixmap mask;
277 WMFont *font = scr->info_text_font;
278 int side = 0;
279 int ty;
280 int bx, by;
282 if (scr->balloon->contents)
283 XFreePixmap(dpy, scr->balloon->contents);
285 width = getMaxStringWidth(font, text) + 16;
286 height = countLines(text) * WMFontHeight(font) + 4;
288 if (height < 16)
289 height = 16;
290 if (width < height)
291 width = height;
293 if (x + width > scr->scr_width) {
294 side = RIGHT;
295 bx = x - width + w / 2;
296 if (bx < 0)
297 bx = 0;
298 } else {
299 side = LEFT;
300 bx = x + w / 2;
302 if (bx + width > scr->scr_width)
303 bx = scr->scr_width - width;
305 if (y - (height + SPACE) < 0) {
306 side |= TOP;
307 by = y + h - 1;
308 ty = SPACE;
309 } else {
310 side |= BOTTOM;
311 by = y - (height + SPACE);
312 ty = 0;
314 pixmap = makePixmap(scr, width, height, side, &mask);
316 drawMultiLineString(scr->wmscreen, pixmap, scr->black, font, 8, ty + 2, text, strlen(text));
318 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
319 scr->balloon->contents = pixmap;
321 XResizeWindow(dpy, scr->balloon->window, width, height + SPACE);
322 XShapeCombineMask(dpy, scr->balloon->window, ShapeBounding, 0, 0, mask, ShapeSet);
323 XFreePixmap(dpy, mask);
324 XMoveWindow(dpy, scr->balloon->window, bx, by);
325 XMapRaised(dpy, scr->balloon->window);
327 scr->balloon->mapped = 1;
329 #else /* !SHAPED_BALLOON */
331 static void showText(WScreen * scr, int x, int y, int h, int w, char *text)
333 int width;
334 int height;
335 Pixmap pixmap;
336 WMFont *font = scr->info_text_font;
338 if (scr->balloon->contents)
339 XFreePixmap(dpy, scr->balloon->contents);
341 width = getMaxStringWidth(font, text) + 8;
342 /*width = WMWidthOfString(font, text, strlen(text))+8; */
343 height = countLines(text) * WMFontHeight(font) + 4;
345 if (x < 0)
346 x = 0;
347 else if (x + width > scr->scr_width - 1)
348 x = scr->scr_width - width;
350 if (y - height - 2 < 0) {
351 y += h;
352 if (y < 0)
353 y = 0;
354 } else {
355 y -= height + 2;
358 if (scr->window_title_texture[0])
359 XSetForeground(dpy, scr->draw_gc, scr->window_title_texture[0]->any.color.pixel);
360 else
361 XSetForeground(dpy, scr->draw_gc, scr->light_pixel);
363 pixmap = XCreatePixmap(dpy, scr->root_win, width, height, scr->w_depth);
364 XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width, height);
366 drawMultiLineString(scr->wmscreen, pixmap, scr->window_title_color[0], font, 4, 2, text, strlen(text));
368 XResizeWindow(dpy, scr->balloon->window, width, height);
369 XMoveWindow(dpy, scr->balloon->window, x, y);
371 XSetWindowBackgroundPixmap(dpy, scr->balloon->window, pixmap);
372 XClearWindow(dpy, scr->balloon->window);
373 XMapRaised(dpy, scr->balloon->window);
375 scr->balloon->contents = pixmap;
377 scr->balloon->mapped = 1;
379 #endif /* !SHAPED_BALLOON */
381 static void showBalloon(WScreen * scr)
383 int x, y;
384 Window foow;
385 unsigned foo, w;
387 if (scr->balloon) {
388 scr->balloon->timer = NULL;
389 scr->balloon->ignoreTimer = 1;
392 if (!XGetGeometry(dpy, scr->balloon->objectWindow, &foow, &x, &y, &w, &foo, &foo, &foo)) {
393 scr->balloon->prevType = 0;
394 return;
396 showText(scr, x, y, scr->balloon->h, w, scr->balloon->text);
399 static void frameBalloon(WObjDescriptor * object)
401 WFrameWindow *fwin = (WFrameWindow *) object->parent;
402 WScreen *scr = fwin->core->screen_ptr;
404 if (fwin->titlebar != object->self || !fwin->flags.is_client_window_frame) {
405 wBalloonHide(scr);
406 return;
408 if (fwin->title && fwin->flags.incomplete_title) {
409 scr->balloon->h = (fwin->titlebar ? fwin->titlebar->height : 0);
410 scr->balloon->text = wstrdup(fwin->title);
411 scr->balloon->objectWindow = fwin->core->window;
412 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
416 static void miniwindowBalloon(WObjDescriptor * object)
418 WIcon *icon = (WIcon *) object->parent;
419 WScreen *scr = icon->core->screen_ptr;
421 if (!icon->icon_name) {
422 wBalloonHide(scr);
423 return;
425 scr->balloon->h = icon->core->height;
426 scr->balloon->text = wstrdup(icon->icon_name);
427 scr->balloon->objectWindow = icon->core->window;
428 if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_APPICON)
429 && scr->balloon->ignoreTimer) {
430 XUnmapWindow(dpy, scr->balloon->window);
431 showBalloon(scr);
432 } else {
433 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
437 static void appiconBalloon(WObjDescriptor * object)
439 WAppIcon *aicon = (WAppIcon *) object->parent;
440 WScreen *scr = aicon->icon->core->screen_ptr;
441 char *tmp;
443 if (aicon->command && aicon->wm_class) {
444 int len = strlen(aicon->command) + strlen(aicon->wm_class) + 8;
445 tmp = wmalloc(len);
446 snprintf(tmp, len, "%s\n(%s)", aicon->wm_class, aicon->command);
447 scr->balloon->text = tmp;
448 } else if (aicon->command) {
449 scr->balloon->text = wstrdup(aicon->command);
450 } else if (aicon->wm_class) {
451 scr->balloon->text = wstrdup(aicon->wm_class);
452 } else {
453 wBalloonHide(scr);
454 return;
456 scr->balloon->h = aicon->icon->core->height - 2;
458 scr->balloon->objectWindow = aicon->icon->core->window;
459 if ((scr->balloon->prevType == object->parent_type || scr->balloon->prevType == WCLASS_MINIWINDOW)
460 && scr->balloon->ignoreTimer) {
461 XUnmapWindow(dpy, scr->balloon->window);
462 showBalloon(scr);
463 } else {
464 scr->balloon->timer = WMAddTimerHandler(BALLOON_DELAY, (WMCallback *) showBalloon, scr);
468 void wBalloonInitialize(WScreen * scr)
470 WBalloon *bal;
471 XSetWindowAttributes attribs;
472 unsigned long vmask;
474 bal = wmalloc(sizeof(WBalloon));
475 memset(bal, 0, sizeof(WBalloon));
477 scr->balloon = bal;
479 vmask = CWSaveUnder | CWOverrideRedirect | CWColormap | CWBackPixel | CWBorderPixel;
480 attribs.save_under = True;
481 attribs.override_redirect = True;
482 attribs.colormap = scr->w_colormap;
483 attribs.background_pixel = scr->icon_back_texture->normal.pixel;
484 attribs.border_pixel = 0; /* do not care */
486 bal->window = XCreateWindow(dpy, scr->root_win, 1, 1, 10, 10, 1,
487 scr->w_depth, CopyFromParent, scr->w_visual, vmask, &attribs);
488 #if 0
489 /* select EnterNotify to so that the balloon will be unmapped
490 * when the pointer is moved over it */
491 XSelectInput(dpy, bal->window, EnterWindowMask);
492 #endif
495 void wBalloonEnteredObject(WScreen * scr, WObjDescriptor * object)
497 WBalloon *balloon = scr->balloon;
499 if (balloon->timer) {
500 WMDeleteTimerHandler(balloon->timer);
501 balloon->timer = NULL;
502 balloon->ignoreTimer = 0;
505 if (scr->balloon->text)
506 wfree(scr->balloon->text);
507 scr->balloon->text = NULL;
509 if (!object) {
510 wBalloonHide(scr);
511 balloon->ignoreTimer = 0;
512 return;
514 switch (object->parent_type) {
515 case WCLASS_FRAME:
516 if (wPreferences.window_balloon) {
517 frameBalloon(object);
519 break;
521 case WCLASS_DOCK_ICON:
522 if (object->parent != scr->clip_icon && wPreferences.appicon_balloon)
523 appiconBalloon(object);
524 else
525 wBalloonHide(scr);
526 break;
528 case WCLASS_MINIWINDOW:
529 if (wPreferences.miniwin_balloon) {
530 miniwindowBalloon(object);
532 break;
533 case WCLASS_APPICON:
534 if (wPreferences.appicon_balloon)
535 appiconBalloon(object);
536 break;
537 default:
538 wBalloonHide(scr);
539 break;
541 scr->balloon->prevType = object->parent_type;
544 void wBalloonHide(WScreen * scr)
546 if (scr) {
547 if (scr->balloon->mapped) {
548 XUnmapWindow(dpy, scr->balloon->window);
549 scr->balloon->mapped = 0;
550 } else if (scr->balloon->timer) {
551 WMDeleteTimerHandler(scr->balloon->timer);
552 scr->balloon->timer = NULL;
554 scr->balloon->prevType = 0;
558 #endif