Avoid unnecessary switchpanel icon redraws.
[wmaker-crm.git] / src / switchpanel.c
blob60afb41e631e9116feafc37610b2726b569130b1
1 /*
2 * Window Maker window manager
4 * Copyright (c) 1997-2004 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 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/time.h>
27 #include "WindowMaker.h"
28 #include "screen.h"
29 #include "framewin.h"
30 #include "icon.h"
31 #include "window.h"
32 #include "defaults.h"
33 #include "switchpanel.h"
34 #include "misc.h"
35 #include "xinerama.h"
37 extern Atom _XA_WM_IGNORE_FOCUS_EVENTS;
39 #ifdef SHAPE
40 #include <X11/extensions/shape.h>
42 extern Bool wShapeSupported;
43 #endif
45 struct SwitchPanel {
46 WScreen *scr;
47 WMWindow *win;
48 WMFrame *iconBox;
50 WMArray *icons;
51 WMArray *images;
52 WMArray *windows;
53 WMArray *flags;
54 RImage *bg;
55 int current;
56 int firstVisible;
57 int visibleCount;
59 WMLabel *label;
61 RImage *tileTmp;
62 RImage *tile;
64 WMFont *font;
65 WMColor *white;
68 extern WPreferences wPreferences;
70 #define BORDER_SPACE 10
71 #define ICON_SIZE 48
72 #define ICON_TILE_SIZE 64
73 #define LABEL_HEIGHT 25
74 #define SCREEN_BORDER_SPACING 2*20
75 #define SCROLL_STEPS (ICON_TILE_SIZE/2)
77 #define ICON_SELECTED (1<<1)
78 #define ICON_DIM (1<<2)
80 static int canReceiveFocus(WWindow *wwin)
82 if (wwin->frame->workspace != wwin->screen_ptr->current_workspace)
83 return 0;
85 if (wPreferences.cycle_active_head_only &&
86 wGetHeadForWindow(wwin) != wGetHeadForPointerLocation(wwin->screen_ptr))
87 return 0;
89 if (WFLAGP(wwin, no_focusable))
90 return 0;
92 if (!wwin->flags.mapped) {
93 if (!wwin->flags.shaded && !wwin->flags.miniaturized && !wwin->flags.hidden)
94 return 0;
95 else
96 return -1;
99 return 1;
102 static Bool sameWindowClass(WWindow *wwin, WWindow *curwin)
104 if (!wwin->wm_class || !curwin->wm_class)
105 return False;
106 if (strcmp(wwin->wm_class, curwin->wm_class))
107 return False;
109 return True;
112 static void changeImage(WSwitchPanel *panel, int idecks, int selected, Bool dim, Bool force)
114 WMFrame *icon = WMGetFromArray(panel->icons, idecks);
115 RImage *image = WMGetFromArray(panel->images, idecks);
116 char flags = (char) WMGetFromArray(panel->flags, idecks);
117 char desired = 0;
119 if (selected)
120 desired |= ICON_SELECTED;
121 if (dim)
122 desired |= ICON_DIM;
124 if (flags == desired && !force)
125 return;
127 WMReplaceInArray(panel->flags, idecks, desired);
129 if (!panel->bg && !panel->tile && !selected)
130 WMSetFrameRelief(icon, WRFlat);
132 if (image && icon) {
133 RImage *back;
134 int opaq = (dim) ? 75 : 255;
135 RImage *tile;
136 WMPoint pos;
137 Pixmap p;
139 if (canReceiveFocus(WMGetFromArray(panel->windows, idecks)) < 0)
140 opaq = 50;
142 pos = WMGetViewPosition(WMWidgetView(icon));
143 back = panel->tileTmp;
144 if (panel->bg) {
145 RCopyArea(back, panel->bg,
146 BORDER_SPACE + pos.x - panel->firstVisible * ICON_TILE_SIZE,
147 BORDER_SPACE + pos.y, back->width, back->height, 0, 0);
148 } else {
149 RColor color;
150 WMScreen *wscr = WMWidgetScreen(icon);
151 color.red = 255;
152 color.red = WMRedComponentOfColor(WMGrayColor(wscr)) >> 8;
153 color.green = WMGreenComponentOfColor(WMGrayColor(wscr)) >> 8;
154 color.blue = WMBlueComponentOfColor(WMGrayColor(wscr)) >> 8;
155 RFillImage(back, &color);
158 if (selected) {
159 tile = panel->tile;
160 RCombineArea(back, tile, 0, 0, tile->width, tile->height,
161 (back->width - tile->width) / 2, (back->height - tile->height) / 2);
164 RCombineAreaWithOpaqueness(back, image, 0, 0, image->width, image->height,
165 (back->width - image->width) / 2, (back->height - image->height) / 2,
166 opaq);
168 RConvertImage(panel->scr->rcontext, back, &p);
169 XSetWindowBackgroundPixmap(dpy, WMWidgetXID(icon), p);
170 XClearWindow(dpy, WMWidgetXID(icon));
171 XFreePixmap(dpy, p);
174 if (!panel->bg && !panel->tile && selected)
175 WMSetFrameRelief(icon, WRSimple);
178 static void addIconForWindow(WSwitchPanel *panel, WMWidget *parent, WWindow *wwin, int x, int y)
180 WMFrame *icon = WMCreateFrame(parent);
181 RImage *image = NULL;
183 WMSetFrameRelief(icon, WRFlat);
184 WMResizeWidget(icon, ICON_TILE_SIZE, ICON_TILE_SIZE);
185 WMMoveWidget(icon, x, y);
187 if (!WFLAGP(wwin, always_user_icon) && wwin->net_icon_image)
188 image = RRetainImage(wwin->net_icon_image);
190 /* get_icon_image() includes the default icon image */
191 if (!image)
192 image = get_icon_image(panel->scr, wwin->wm_instance, wwin->wm_class, ICON_TILE_SIZE);
194 /* We must resize the icon size (~64) to the switchpanel icon size (~48) */
195 image = wIconValidateIconSize(image, ICON_SIZE);
197 WMAddToArray(panel->images, image);
198 WMAddToArray(panel->icons, icon);
201 static void scrollIcons(WSwitchPanel *panel, int delta)
203 int nfirst = panel->firstVisible + delta;
204 int i;
205 int count = WMGetArrayItemCount(panel->windows);
206 Bool dim;
208 if (count <= panel->visibleCount)
209 return;
211 if (nfirst < 0)
212 nfirst = 0;
213 else if (nfirst >= count - panel->visibleCount)
214 nfirst = count - panel->visibleCount;
216 if (nfirst == panel->firstVisible)
217 return;
219 WMMoveWidget(panel->iconBox, -nfirst * ICON_TILE_SIZE, 0);
221 panel->firstVisible = nfirst;
223 for (i = panel->firstVisible; i < panel->firstVisible + panel->visibleCount; i++) {
224 if (i == panel->current)
225 continue;
226 dim = ((char) WMGetFromArray(panel->flags, i) & ICON_DIM);
227 changeImage(panel, i, 0, dim, True);
232 * 0 1 2
233 * 3 4 5
234 * 6 7 8
236 static RImage *assemblePuzzleImage(RImage **images, int width, int height)
238 RImage *img = RCreateImage(width, height, 1);
239 RImage *tmp;
240 int tw, th;
241 RColor color;
242 if (!img)
243 return NULL;
245 color.red = 0;
246 color.green = 0;
247 color.blue = 0;
248 color.alpha = 255;
250 RFillImage(img, &color);
252 tw = width - images[0]->width - images[2]->width;
253 th = height - images[0]->height - images[6]->height;
255 if (tw <= 0 || th <= 0)
256 return NULL;
258 /* top */
259 if (tw > 0) {
260 tmp = RSmoothScaleImage(images[1], tw, images[1]->height);
261 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[0]->width, 0);
262 RReleaseImage(tmp);
264 /* bottom */
265 if (tw > 0) {
266 tmp = RSmoothScaleImage(images[7], tw, images[7]->height);
267 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[6]->width, height - images[6]->height);
268 RReleaseImage(tmp);
270 /* left */
271 if (th > 0) {
272 tmp = RSmoothScaleImage(images[3], images[3]->width, th);
273 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, 0, images[0]->height);
274 RReleaseImage(tmp);
276 /* right */
277 if (th > 0) {
278 tmp = RSmoothScaleImage(images[5], images[5]->width, th);
279 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, width - images[5]->width, images[2]->height);
280 RReleaseImage(tmp);
282 /* center */
283 if (tw > 0 && th > 0) {
284 tmp = RSmoothScaleImage(images[4], tw, th);
285 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[0]->width, images[0]->height);
286 RReleaseImage(tmp);
289 /* corners */
290 RCopyArea(img, images[0], 0, 0, images[0]->width, images[0]->height, 0, 0);
291 RCopyArea(img, images[2], 0, 0, images[2]->width, images[2]->height, width - images[2]->width, 0);
292 RCopyArea(img, images[6], 0, 0, images[6]->width, images[6]->height, 0, height - images[6]->height);
293 RCopyArea(img, images[8], 0, 0, images[8]->width, images[8]->height,
294 width - images[8]->width, height - images[8]->height);
296 return img;
299 static RImage *createBackImage(int width, int height)
301 return assemblePuzzleImage(wPreferences.swbackImage, width, height);
304 static RImage *getTile(void)
306 RImage *stile;
308 if (!wPreferences.swtileImage)
309 return NULL;
311 stile = RScaleImage(wPreferences.swtileImage, ICON_TILE_SIZE, ICON_TILE_SIZE);
312 if (!stile)
313 return wPreferences.swtileImage;
315 return stile;
318 static void drawTitle(WSwitchPanel *panel, int idecks, char *title)
320 char *ntitle;
321 int width = WMWidgetWidth(panel->win);
322 int x;
324 if (title)
325 ntitle = ShrinkString(panel->font, title, width - 2 * BORDER_SPACE);
326 else
327 ntitle = NULL;
329 if (panel->bg) {
330 if (ntitle) {
331 if (strcmp(ntitle, title) != 0) {
332 x = BORDER_SPACE;
333 } else {
334 int w = WMWidthOfString(panel->font, ntitle, strlen(ntitle));
336 x = BORDER_SPACE + (idecks - panel->firstVisible) * ICON_TILE_SIZE +
337 ICON_TILE_SIZE / 2 - w / 2;
338 if (x < BORDER_SPACE)
339 x = BORDER_SPACE;
340 else if (x + w > width - BORDER_SPACE)
341 x = width - BORDER_SPACE - w;
345 XClearWindow(dpy, WMWidgetXID(panel->win));
346 if (ntitle)
347 WMDrawString(panel->scr->wmscreen,
348 WMWidgetXID(panel->win),
349 panel->white, panel->font,
351 WMWidgetHeight(panel->win) - BORDER_SPACE - LABEL_HEIGHT +
352 WMFontHeight(panel->font) / 2, ntitle, strlen(ntitle));
353 } else {
354 if (ntitle)
355 WMSetLabelText(panel->label, ntitle);
358 if (ntitle)
359 free(ntitle);
362 static WMArray *makeWindowListArray(WWindow *curwin, int include_unmapped, Bool class_only)
364 WMArray *windows = WMCreateArray(10);
365 int fl;
366 WWindow *wwin;
368 for (fl = 0; fl < 2; fl++) {
369 for (wwin = curwin; wwin; wwin = wwin->prev) {
370 if (((!fl && canReceiveFocus(wwin) > 0) || (fl && canReceiveFocus(wwin) < 0)) &&
371 (wwin->flags.mapped || include_unmapped)) {
372 if (class_only)
373 if (!sameWindowClass(wwin, curwin))
374 continue;
376 if (!WFLAGP(wwin, skip_switchpanel))
377 WMAddToArray(windows, wwin);
380 wwin = curwin;
381 /* start over from the beginning of the list */
382 while (wwin->next)
383 wwin = wwin->next;
385 for (wwin = curwin; wwin && wwin != curwin; wwin = wwin->prev) {
386 if (((!fl && canReceiveFocus(wwin) > 0) || (fl && canReceiveFocus(wwin) < 0)) &&
387 (wwin->flags.mapped || include_unmapped)) {
388 if (class_only)
389 if (!sameWindowClass(wwin, curwin))
390 continue;
391 if (!WFLAGP(wwin, skip_switchpanel))
392 WMAddToArray(windows, wwin);
397 return windows;
400 static WMArray *makeWindowFlagsArray(int count)
402 WMArray *flags = WMCreateArray(1);
403 int i;
405 for (i = 0; i < count; i++)
406 WMAddToArray(flags, (char) 0);
408 return flags;
411 WSwitchPanel *wInitSwitchPanel(WScreen *scr, WWindow *curwin, Bool class_only)
413 WWindow *wwin;
414 WSwitchPanel *panel = wmalloc(sizeof(WSwitchPanel));
415 WMFrame *viewport;
416 int i, width, height, iconsThatFitCount, count;
417 WMRect rect = wGetRectForHead(scr, wGetHeadForPointerLocation(scr));
419 panel->scr = scr;
420 panel->windows = makeWindowListArray(curwin, wPreferences.swtileImage != NULL, class_only);
421 count = WMGetArrayItemCount(panel->windows);
422 if (count)
423 panel->flags = makeWindowFlagsArray(count);
425 if (count == 0) {
426 WMFreeArray(panel->windows);
427 wfree(panel);
428 return NULL;
431 width = ICON_TILE_SIZE * count;
432 iconsThatFitCount = count;
434 if (width > rect.size.width) {
435 iconsThatFitCount = (rect.size.width - SCREEN_BORDER_SPACING) / ICON_TILE_SIZE;
436 width = iconsThatFitCount * ICON_TILE_SIZE;
439 panel->visibleCount = iconsThatFitCount;
441 if (!wPreferences.swtileImage)
442 return panel;
444 height = LABEL_HEIGHT + ICON_TILE_SIZE;
446 panel->tileTmp = RCreateImage(ICON_TILE_SIZE, ICON_TILE_SIZE, 1);
447 panel->tile = getTile();
448 if (panel->tile && wPreferences.swbackImage[8])
449 panel->bg = createBackImage(width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
451 if (!panel->tileTmp || !panel->tile) {
452 if (panel->bg)
453 RReleaseImage(panel->bg);
454 panel->bg = NULL;
455 if (panel->tile)
456 RReleaseImage(panel->tile);
457 panel->tile = NULL;
458 if (panel->tileTmp)
459 RReleaseImage(panel->tileTmp);
460 panel->tileTmp = NULL;
463 panel->white = WMWhiteColor(scr->wmscreen);
464 panel->font = WMBoldSystemFontOfSize(scr->wmscreen, 12);
465 panel->icons = WMCreateArray(count);
466 panel->images = WMCreateArray(count);
468 panel->win = WMCreateWindow(scr->wmscreen, "");
470 if (!panel->bg) {
471 WMFrame *frame = WMCreateFrame(panel->win);
472 WMColor *darkGray = WMDarkGrayColor(scr->wmscreen);
473 WMSetFrameRelief(frame, WRSimple);
474 WMSetViewExpandsToParent(WMWidgetView(frame), 0, 0, 0, 0);
476 panel->label = WMCreateLabel(panel->win);
477 WMResizeWidget(panel->label, width, LABEL_HEIGHT);
478 WMMoveWidget(panel->label, BORDER_SPACE, BORDER_SPACE + ICON_TILE_SIZE + 5);
479 WMSetLabelRelief(panel->label, WRSimple);
480 WMSetWidgetBackgroundColor(panel->label, darkGray);
481 WMSetLabelFont(panel->label, panel->font);
482 WMSetLabelTextColor(panel->label, panel->white);
484 WMReleaseColor(darkGray);
485 height += 5;
488 WMResizeWidget(panel->win, width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
490 viewport = WMCreateFrame(panel->win);
491 WMResizeWidget(viewport, width, ICON_TILE_SIZE);
492 WMMoveWidget(viewport, BORDER_SPACE, BORDER_SPACE);
493 WMSetFrameRelief(viewport, WRFlat);
495 panel->iconBox = WMCreateFrame(viewport);
496 WMMoveWidget(panel->iconBox, 0, 0);
497 WMResizeWidget(panel->iconBox, ICON_TILE_SIZE * count, ICON_TILE_SIZE);
498 WMSetFrameRelief(panel->iconBox, WRFlat);
500 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
501 addIconForWindow(panel, panel->iconBox, wwin, i * ICON_TILE_SIZE, 0);
504 WMMapSubwidgets(panel->win);
505 WMRealizeWidget(panel->win);
507 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
508 changeImage(panel, i, 0, False, True);
511 if (panel->bg) {
512 Pixmap pixmap, mask;
514 RConvertImageMask(scr->rcontext, panel->bg, &pixmap, &mask, 250);
516 XSetWindowBackgroundPixmap(dpy, WMWidgetXID(panel->win), pixmap);
518 #ifdef SHAPE
519 if (mask && wShapeSupported)
520 XShapeCombineMask(dpy, WMWidgetXID(panel->win), ShapeBounding, 0, 0, mask, ShapeSet);
521 #endif
522 if (pixmap)
523 XFreePixmap(dpy, pixmap);
525 if (mask)
526 XFreePixmap(dpy, mask);
530 WMPoint center;
531 center = wGetPointToCenterRectInHead(scr, wGetHeadForPointerLocation(scr),
532 width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
533 WMMoveWidget(panel->win, center.x, center.y);
536 panel->current = WMGetFirstInArray(panel->windows, curwin);
537 if (panel->current >= 0)
538 changeImage(panel, panel->current, 1, False, False);
540 WMMapWidget(panel->win);
542 return panel;
545 void wSwitchPanelDestroy(WSwitchPanel *panel)
547 int i;
548 RImage *image;
550 if (panel->win) {
551 Window info_win = panel->scr->info_window;
552 XEvent ev;
553 ev.xclient.type = ClientMessage;
554 ev.xclient.message_type = _XA_WM_IGNORE_FOCUS_EVENTS;
555 ev.xclient.format = 32;
556 ev.xclient.data.l[0] = True;
558 XSendEvent(dpy, info_win, True, EnterWindowMask, &ev);
559 WMUnmapWidget(panel->win);
561 ev.xclient.data.l[0] = False;
562 XSendEvent(dpy, info_win, True, EnterWindowMask, &ev);
565 if (panel->images) {
566 WM_ITERATE_ARRAY(panel->images, image, i) {
567 if (image)
568 RReleaseImage(image);
570 WMFreeArray(panel->images);
573 if (panel->win)
574 WMDestroyWidget(panel->win);
576 if (panel->icons)
577 WMFreeArray(panel->icons);
579 if (panel->flags)
580 WMFreeArray(panel->flags);
582 WMFreeArray(panel->windows);
584 if (panel->tile)
585 RReleaseImage(panel->tile);
587 if (panel->tileTmp)
588 RReleaseImage(panel->tileTmp);
590 if (panel->bg)
591 RReleaseImage(panel->bg);
593 if (panel->font)
594 WMReleaseFont(panel->font);
596 if (panel->white)
597 WMReleaseColor(panel->white);
599 wfree(panel);
602 WWindow *wSwitchPanelSelectNext(WSwitchPanel *panel, int back, int ignore_minimized, Bool class_only)
604 WWindow *wwin, *curwin, *tmpwin;
605 int count = WMGetArrayItemCount(panel->windows);
606 int orig = panel->current;
607 int i;
608 Bool dim = False;
610 if (count == 0)
611 return NULL;
613 if (!wPreferences.cycle_ignore_minimized)
614 ignore_minimized = False;
616 if (ignore_minimized && canReceiveFocus(WMGetFromArray(panel->windows, (count + panel->current) % count)) < 0)
617 ignore_minimized = False;
619 curwin = WMGetFromArray(panel->windows, orig);
620 do {
621 do {
622 if (back)
623 panel->current--;
624 else
625 panel->current++;
627 panel->current= (count + panel->current) % count;
628 wwin = WMGetFromArray(panel->windows, panel->current);
630 if (!class_only)
631 break;
632 if (panel->current == orig)
633 break;
634 } while (!sameWindowClass(wwin, curwin));
635 } while (ignore_minimized && panel->current != orig && canReceiveFocus(wwin) < 0);
637 WM_ITERATE_ARRAY(panel->windows, tmpwin, i) {
638 if (i == panel->current)
639 continue;
640 if (!class_only || sameWindowClass(tmpwin, curwin))
641 changeImage(panel, i, 0, False, False);
642 else {
643 if (i == orig)
644 dim = True;
645 changeImage(panel, i, 0, True, False);
650 if (panel->current < panel->firstVisible)
651 scrollIcons(panel, panel->current - panel->firstVisible);
652 else if (panel->current - panel->firstVisible >= panel->visibleCount)
653 scrollIcons(panel, panel->current - panel->firstVisible - panel->visibleCount + 1);
655 if (panel->win) {
656 drawTitle(panel, panel->current, wwin->frame->title);
657 if (panel->current != orig)
658 changeImage(panel, orig, 0, dim, False);
659 changeImage(panel, panel->current, 1, False, False);
662 return wwin;
665 WWindow *wSwitchPanelSelectFirst(WSwitchPanel *panel, int back)
667 WWindow *wwin;
668 int count = WMGetArrayItemCount(panel->windows);
669 char *title;
670 int i;
672 if (count == 0)
673 return NULL;
675 if (back) {
676 panel->current = count - 1;
677 scrollIcons(panel, count);
678 } else {
679 panel->current = 0;
680 scrollIcons(panel, -count);
683 wwin = WMGetFromArray(panel->windows, panel->current);
684 title = wwin->frame->title;
686 if (panel->win) {
687 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
688 changeImage(panel, i, i == panel->current, False, False);
690 drawTitle(panel, panel->current, title);
693 return wwin;
696 WWindow *wSwitchPanelHandleEvent(WSwitchPanel *panel, XEvent *event)
698 WMFrame *icon;
699 int i;
700 int focus = -1;
702 if (!panel->win)
703 return NULL;
705 if (event->type == MotionNotify) {
706 WM_ITERATE_ARRAY(panel->icons, icon, i) {
707 if (WMWidgetXID(icon) == event->xmotion.window) {
708 focus = i;
709 break;
714 if (focus >= 0 && panel->current != focus) {
715 WWindow *wwin;
717 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
718 changeImage(panel, i, i == focus, False, False);
720 panel->current = focus;
722 wwin = WMGetFromArray(panel->windows, focus);
724 drawTitle(panel, panel->current, wwin->frame->title);
726 return wwin;
729 return NULL;
732 Window wSwitchPanelGetWindow(WSwitchPanel *swpanel)
734 if (!swpanel->win)
735 return None;
737 return WMWidgetXID(swpanel->win);