f616bfaf3377c2b1f8bfce15855de82b74c30db6
[wmaker-crm.git] / src / switchpanel.c
blobf616bfaf3377c2b1f8bfce15855de82b74c30db6
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 <stdint.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/time.h>
28 #include "WindowMaker.h"
29 #include "screen.h"
30 #include "framewin.h"
31 #include "icon.h"
32 #include "window.h"
33 #include "defaults.h"
34 #include "switchpanel.h"
35 #include "misc.h"
36 #include "xinerama.h"
38 extern Atom _XA_WM_IGNORE_FOCUS_EVENTS;
40 #ifdef SHAPE
41 #include <X11/extensions/shape.h>
43 extern Bool wShapeSupported;
44 #endif
46 struct SwitchPanel {
47 WScreen *scr;
48 WMWindow *win;
49 WMFrame *iconBox;
51 WMArray *icons;
52 WMArray *images;
53 WMArray *windows;
54 WMArray *flags;
55 RImage *bg;
56 int current;
57 int firstVisible;
58 int visibleCount;
60 WMLabel *label;
62 RImage *tileTmp;
63 RImage *tile;
65 WMFont *font;
66 WMColor *white;
69 extern WPreferences wPreferences;
71 #define BORDER_SPACE 10
72 #define ICON_SIZE 48
73 #define ICON_TILE_SIZE 64
74 #define LABEL_HEIGHT 25
75 #define SCREEN_BORDER_SPACING 2*20
76 #define SCROLL_STEPS (ICON_TILE_SIZE/2)
78 #define ICON_SELECTED (1<<1)
79 #define ICON_DIM (1<<2)
81 static int canReceiveFocus(WWindow *wwin)
83 if (wwin->frame->workspace != wwin->screen_ptr->current_workspace)
84 return 0;
86 if (wPreferences.cycle_active_head_only &&
87 wGetHeadForWindow(wwin) != wGetHeadForPointerLocation(wwin->screen_ptr))
88 return 0;
90 if (WFLAGP(wwin, no_focusable))
91 return 0;
93 if (!wwin->flags.mapped) {
94 if (!wwin->flags.shaded && !wwin->flags.miniaturized && !wwin->flags.hidden)
95 return 0;
96 else
97 return -1;
100 return 1;
103 static Bool sameWindowClass(WWindow *wwin, WWindow *curwin)
105 if (!wwin->wm_class || !curwin->wm_class)
106 return False;
107 if (strcmp(wwin->wm_class, curwin->wm_class))
108 return False;
110 return True;
113 static void changeImage(WSwitchPanel *panel, int idecks, int selected, Bool dim, Bool force)
115 WMFrame *icon = NULL;
116 RImage *image = NULL;
117 char flags = 0;
118 char desired = 0;
120 /* This whole function is a no-op if we aren't drawing the panel */
121 if (!wPreferences.swtileImage)
122 return;
124 icon = WMGetFromArray(panel->icons, idecks);
125 image = WMGetFromArray(panel->images, idecks);
126 flags = (char) (uintptr_t) WMGetFromArray(panel->flags, idecks);
128 if (selected)
129 desired |= ICON_SELECTED;
130 if (dim)
131 desired |= ICON_DIM;
133 if (flags == desired && !force)
134 return;
136 WMReplaceInArray(panel->flags, idecks, (void *) (uintptr_t) desired);
138 if (!panel->bg && !panel->tile && !selected)
139 WMSetFrameRelief(icon, WRFlat);
141 if (image && icon) {
142 RImage *back;
143 int opaq = (dim) ? 75 : 255;
144 RImage *tile;
145 WMPoint pos;
146 Pixmap p;
148 if (canReceiveFocus(WMGetFromArray(panel->windows, idecks)) < 0)
149 opaq = 50;
151 pos = WMGetViewPosition(WMWidgetView(icon));
152 back = panel->tileTmp;
153 if (panel->bg) {
154 RCopyArea(back, panel->bg,
155 BORDER_SPACE + pos.x - panel->firstVisible * ICON_TILE_SIZE,
156 BORDER_SPACE + pos.y, back->width, back->height, 0, 0);
157 } else {
158 RColor color;
159 WMScreen *wscr = WMWidgetScreen(icon);
160 color.red = 255;
161 color.red = WMRedComponentOfColor(WMGrayColor(wscr)) >> 8;
162 color.green = WMGreenComponentOfColor(WMGrayColor(wscr)) >> 8;
163 color.blue = WMBlueComponentOfColor(WMGrayColor(wscr)) >> 8;
164 RFillImage(back, &color);
167 if (selected) {
168 tile = panel->tile;
169 RCombineArea(back, tile, 0, 0, tile->width, tile->height,
170 (back->width - tile->width) / 2, (back->height - tile->height) / 2);
173 RCombineAreaWithOpaqueness(back, image, 0, 0, image->width, image->height,
174 (back->width - image->width) / 2, (back->height - image->height) / 2,
175 opaq);
177 RConvertImage(panel->scr->rcontext, back, &p);
178 XSetWindowBackgroundPixmap(dpy, WMWidgetXID(icon), p);
179 XClearWindow(dpy, WMWidgetXID(icon));
180 XFreePixmap(dpy, p);
183 if (!panel->bg && !panel->tile && selected)
184 WMSetFrameRelief(icon, WRSimple);
187 static void addIconForWindow(WSwitchPanel *panel, WMWidget *parent, WWindow *wwin, int x, int y)
189 WMFrame *icon = WMCreateFrame(parent);
190 RImage *image = NULL;
192 WMSetFrameRelief(icon, WRFlat);
193 WMResizeWidget(icon, ICON_TILE_SIZE, ICON_TILE_SIZE);
194 WMMoveWidget(icon, x, y);
196 if (!WFLAGP(wwin, always_user_icon) && wwin->net_icon_image)
197 image = RRetainImage(wwin->net_icon_image);
199 /* get_icon_image() includes the default icon image */
200 if (!image)
201 image = get_icon_image(panel->scr, wwin->wm_instance, wwin->wm_class, ICON_TILE_SIZE);
203 /* We must resize the icon size (~64) to the switchpanel icon size (~48) */
204 image = wIconValidateIconSize(image, ICON_SIZE);
206 WMAddToArray(panel->images, image);
207 WMAddToArray(panel->icons, icon);
210 static void scrollIcons(WSwitchPanel *panel, int delta)
212 int nfirst = panel->firstVisible + delta;
213 int i;
214 int count = WMGetArrayItemCount(panel->windows);
215 Bool dim;
217 if (count <= panel->visibleCount)
218 return;
220 if (nfirst < 0)
221 nfirst = 0;
222 else if (nfirst >= count - panel->visibleCount)
223 nfirst = count - panel->visibleCount;
225 if (nfirst == panel->firstVisible)
226 return;
228 WMMoveWidget(panel->iconBox, -nfirst * ICON_TILE_SIZE, 0);
230 panel->firstVisible = nfirst;
232 for (i = panel->firstVisible; i < panel->firstVisible + panel->visibleCount; i++) {
233 if (i == panel->current)
234 continue;
235 dim = ((char) (uintptr_t) WMGetFromArray(panel->flags, i) & ICON_DIM);
236 changeImage(panel, i, 0, dim, True);
241 * 0 1 2
242 * 3 4 5
243 * 6 7 8
245 static RImage *assemblePuzzleImage(RImage **images, int width, int height)
247 RImage *img = RCreateImage(width, height, 1);
248 RImage *tmp;
249 int tw, th;
250 RColor color;
251 if (!img)
252 return NULL;
254 color.red = 0;
255 color.green = 0;
256 color.blue = 0;
257 color.alpha = 255;
259 RFillImage(img, &color);
261 tw = width - images[0]->width - images[2]->width;
262 th = height - images[0]->height - images[6]->height;
264 if (tw <= 0 || th <= 0)
265 return NULL;
267 /* top */
268 if (tw > 0) {
269 tmp = RSmoothScaleImage(images[1], tw, images[1]->height);
270 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[0]->width, 0);
271 RReleaseImage(tmp);
273 /* bottom */
274 if (tw > 0) {
275 tmp = RSmoothScaleImage(images[7], tw, images[7]->height);
276 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[6]->width, height - images[6]->height);
277 RReleaseImage(tmp);
279 /* left */
280 if (th > 0) {
281 tmp = RSmoothScaleImage(images[3], images[3]->width, th);
282 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, 0, images[0]->height);
283 RReleaseImage(tmp);
285 /* right */
286 if (th > 0) {
287 tmp = RSmoothScaleImage(images[5], images[5]->width, th);
288 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, width - images[5]->width, images[2]->height);
289 RReleaseImage(tmp);
291 /* center */
292 if (tw > 0 && th > 0) {
293 tmp = RSmoothScaleImage(images[4], tw, th);
294 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[0]->width, images[0]->height);
295 RReleaseImage(tmp);
298 /* corners */
299 RCopyArea(img, images[0], 0, 0, images[0]->width, images[0]->height, 0, 0);
300 RCopyArea(img, images[2], 0, 0, images[2]->width, images[2]->height, width - images[2]->width, 0);
301 RCopyArea(img, images[6], 0, 0, images[6]->width, images[6]->height, 0, height - images[6]->height);
302 RCopyArea(img, images[8], 0, 0, images[8]->width, images[8]->height,
303 width - images[8]->width, height - images[8]->height);
305 return img;
308 static RImage *createBackImage(int width, int height)
310 return assemblePuzzleImage(wPreferences.swbackImage, width, height);
313 static RImage *getTile(void)
315 RImage *stile;
317 if (!wPreferences.swtileImage)
318 return NULL;
320 stile = RScaleImage(wPreferences.swtileImage, ICON_TILE_SIZE, ICON_TILE_SIZE);
321 if (!stile)
322 return wPreferences.swtileImage;
324 return stile;
327 static void drawTitle(WSwitchPanel *panel, int idecks, const char *title)
329 char *ntitle;
330 int width = WMWidgetWidth(panel->win);
331 int x;
333 if (title)
334 ntitle = ShrinkString(panel->font, title, width - 2 * BORDER_SPACE);
335 else
336 ntitle = NULL;
338 if (panel->bg) {
339 if (ntitle) {
340 if (strcmp(ntitle, title) != 0) {
341 x = BORDER_SPACE;
342 } else {
343 int w = WMWidthOfString(panel->font, ntitle, strlen(ntitle));
345 x = BORDER_SPACE + (idecks - panel->firstVisible) * ICON_TILE_SIZE +
346 ICON_TILE_SIZE / 2 - w / 2;
347 if (x < BORDER_SPACE)
348 x = BORDER_SPACE;
349 else if (x + w > width - BORDER_SPACE)
350 x = width - BORDER_SPACE - w;
354 XClearWindow(dpy, WMWidgetXID(panel->win));
355 if (ntitle)
356 WMDrawString(panel->scr->wmscreen,
357 WMWidgetXID(panel->win),
358 panel->white, panel->font,
360 WMWidgetHeight(panel->win) - BORDER_SPACE - LABEL_HEIGHT +
361 WMFontHeight(panel->font) / 2, ntitle, strlen(ntitle));
362 } else {
363 if (ntitle)
364 WMSetLabelText(panel->label, ntitle);
367 if (ntitle)
368 free(ntitle);
371 static WMArray *makeWindowListArray(WWindow *curwin, int include_unmapped, Bool class_only)
373 WMArray *windows = WMCreateArray(10);
374 int fl;
375 WWindow *wwin;
377 for (fl = 0; fl < 2; fl++) {
378 for (wwin = curwin; wwin; wwin = wwin->prev) {
379 if (((!fl && canReceiveFocus(wwin) > 0) || (fl && canReceiveFocus(wwin) < 0)) &&
380 (wwin->flags.mapped || include_unmapped)) {
381 if (class_only)
382 if (!sameWindowClass(wwin, curwin))
383 continue;
385 if (!WFLAGP(wwin, skip_switchpanel))
386 WMAddToArray(windows, wwin);
389 wwin = curwin;
390 /* start over from the beginning of the list */
391 while (wwin->next)
392 wwin = wwin->next;
394 for (wwin = curwin; wwin && wwin != curwin; wwin = wwin->prev) {
395 if (((!fl && canReceiveFocus(wwin) > 0) || (fl && canReceiveFocus(wwin) < 0)) &&
396 (wwin->flags.mapped || include_unmapped)) {
397 if (class_only)
398 if (!sameWindowClass(wwin, curwin))
399 continue;
400 if (!WFLAGP(wwin, skip_switchpanel))
401 WMAddToArray(windows, wwin);
406 return windows;
409 static WMArray *makeWindowFlagsArray(int count)
411 WMArray *flags = WMCreateArray(1);
412 int i;
414 for (i = 0; i < count; i++)
415 WMAddToArray(flags, (char) 0);
417 return flags;
420 WSwitchPanel *wInitSwitchPanel(WScreen *scr, WWindow *curwin, Bool class_only)
422 WWindow *wwin;
423 WSwitchPanel *panel = wmalloc(sizeof(WSwitchPanel));
424 WMFrame *viewport;
425 int i, width, height, iconsThatFitCount, count;
426 WMRect rect = wGetRectForHead(scr, wGetHeadForPointerLocation(scr));
428 panel->scr = scr;
429 panel->windows = makeWindowListArray(curwin, wPreferences.swtileImage != NULL, class_only);
430 count = WMGetArrayItemCount(panel->windows);
431 if (count)
432 panel->flags = makeWindowFlagsArray(count);
434 if (count == 0) {
435 WMFreeArray(panel->windows);
436 wfree(panel);
437 return NULL;
440 width = ICON_TILE_SIZE * count;
441 iconsThatFitCount = count;
443 if (width > rect.size.width) {
444 iconsThatFitCount = (rect.size.width - SCREEN_BORDER_SPACING) / ICON_TILE_SIZE;
445 width = iconsThatFitCount * ICON_TILE_SIZE;
448 panel->visibleCount = iconsThatFitCount;
450 if (!wPreferences.swtileImage)
451 return panel;
453 height = LABEL_HEIGHT + ICON_TILE_SIZE;
455 panel->tileTmp = RCreateImage(ICON_TILE_SIZE, ICON_TILE_SIZE, 1);
456 panel->tile = getTile();
457 if (panel->tile && wPreferences.swbackImage[8])
458 panel->bg = createBackImage(width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
460 if (!panel->tileTmp || !panel->tile) {
461 if (panel->bg)
462 RReleaseImage(panel->bg);
463 panel->bg = NULL;
464 if (panel->tile)
465 RReleaseImage(panel->tile);
466 panel->tile = NULL;
467 if (panel->tileTmp)
468 RReleaseImage(panel->tileTmp);
469 panel->tileTmp = NULL;
472 panel->white = WMWhiteColor(scr->wmscreen);
473 panel->font = WMBoldSystemFontOfSize(scr->wmscreen, 12);
474 panel->icons = WMCreateArray(count);
475 panel->images = WMCreateArray(count);
477 panel->win = WMCreateWindow(scr->wmscreen, "");
479 if (!panel->bg) {
480 WMFrame *frame = WMCreateFrame(panel->win);
481 WMColor *darkGray = WMDarkGrayColor(scr->wmscreen);
482 WMSetFrameRelief(frame, WRSimple);
483 WMSetViewExpandsToParent(WMWidgetView(frame), 0, 0, 0, 0);
485 panel->label = WMCreateLabel(panel->win);
486 WMResizeWidget(panel->label, width, LABEL_HEIGHT);
487 WMMoveWidget(panel->label, BORDER_SPACE, BORDER_SPACE + ICON_TILE_SIZE + 5);
488 WMSetLabelRelief(panel->label, WRSimple);
489 WMSetWidgetBackgroundColor(panel->label, darkGray);
490 WMSetLabelFont(panel->label, panel->font);
491 WMSetLabelTextColor(panel->label, panel->white);
493 WMReleaseColor(darkGray);
494 height += 5;
497 WMResizeWidget(panel->win, width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
499 viewport = WMCreateFrame(panel->win);
500 WMResizeWidget(viewport, width, ICON_TILE_SIZE);
501 WMMoveWidget(viewport, BORDER_SPACE, BORDER_SPACE);
502 WMSetFrameRelief(viewport, WRFlat);
504 panel->iconBox = WMCreateFrame(viewport);
505 WMMoveWidget(panel->iconBox, 0, 0);
506 WMResizeWidget(panel->iconBox, ICON_TILE_SIZE * count, ICON_TILE_SIZE);
507 WMSetFrameRelief(panel->iconBox, WRFlat);
509 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
510 addIconForWindow(panel, panel->iconBox, wwin, i * ICON_TILE_SIZE, 0);
513 WMMapSubwidgets(panel->win);
514 WMRealizeWidget(panel->win);
516 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
517 changeImage(panel, i, 0, False, True);
520 if (panel->bg) {
521 Pixmap pixmap, mask;
523 RConvertImageMask(scr->rcontext, panel->bg, &pixmap, &mask, 250);
525 XSetWindowBackgroundPixmap(dpy, WMWidgetXID(panel->win), pixmap);
527 #ifdef SHAPE
528 if (mask && wShapeSupported)
529 XShapeCombineMask(dpy, WMWidgetXID(panel->win), ShapeBounding, 0, 0, mask, ShapeSet);
530 #endif
531 if (pixmap)
532 XFreePixmap(dpy, pixmap);
534 if (mask)
535 XFreePixmap(dpy, mask);
539 WMPoint center;
540 center = wGetPointToCenterRectInHead(scr, wGetHeadForPointerLocation(scr),
541 width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
542 WMMoveWidget(panel->win, center.x, center.y);
545 panel->current = WMGetFirstInArray(panel->windows, curwin);
546 if (panel->current >= 0)
547 changeImage(panel, panel->current, 1, False, False);
549 WMMapWidget(panel->win);
551 return panel;
554 void wSwitchPanelDestroy(WSwitchPanel *panel)
556 int i;
557 RImage *image;
559 if (panel->win) {
560 Window info_win = panel->scr->info_window;
561 XEvent ev;
562 ev.xclient.type = ClientMessage;
563 ev.xclient.message_type = _XA_WM_IGNORE_FOCUS_EVENTS;
564 ev.xclient.format = 32;
565 ev.xclient.data.l[0] = True;
567 XSendEvent(dpy, info_win, True, EnterWindowMask, &ev);
568 WMUnmapWidget(panel->win);
570 ev.xclient.data.l[0] = False;
571 XSendEvent(dpy, info_win, True, EnterWindowMask, &ev);
574 if (panel->images) {
575 WM_ITERATE_ARRAY(panel->images, image, i) {
576 if (image)
577 RReleaseImage(image);
579 WMFreeArray(panel->images);
582 if (panel->win)
583 WMDestroyWidget(panel->win);
585 if (panel->icons)
586 WMFreeArray(panel->icons);
588 if (panel->flags)
589 WMFreeArray(panel->flags);
591 WMFreeArray(panel->windows);
593 if (panel->tile)
594 RReleaseImage(panel->tile);
596 if (panel->tileTmp)
597 RReleaseImage(panel->tileTmp);
599 if (panel->bg)
600 RReleaseImage(panel->bg);
602 if (panel->font)
603 WMReleaseFont(panel->font);
605 if (panel->white)
606 WMReleaseColor(panel->white);
608 wfree(panel);
611 WWindow *wSwitchPanelSelectNext(WSwitchPanel *panel, int back, int ignore_minimized, Bool class_only)
613 WWindow *wwin, *curwin, *tmpwin;
614 int count = WMGetArrayItemCount(panel->windows);
615 int orig = panel->current;
616 int i;
617 Bool dim = False;
619 if (count == 0)
620 return NULL;
622 if (!wPreferences.cycle_ignore_minimized)
623 ignore_minimized = False;
625 if (ignore_minimized && canReceiveFocus(WMGetFromArray(panel->windows, (count + panel->current) % count)) < 0)
626 ignore_minimized = False;
628 curwin = WMGetFromArray(panel->windows, orig);
629 do {
630 do {
631 if (back)
632 panel->current--;
633 else
634 panel->current++;
636 panel->current= (count + panel->current) % count;
637 wwin = WMGetFromArray(panel->windows, panel->current);
639 if (!class_only)
640 break;
641 if (panel->current == orig)
642 break;
643 } while (!sameWindowClass(wwin, curwin));
644 } while (ignore_minimized && panel->current != orig && canReceiveFocus(wwin) < 0);
646 WM_ITERATE_ARRAY(panel->windows, tmpwin, i) {
647 if (i == panel->current)
648 continue;
649 if (!class_only || sameWindowClass(tmpwin, curwin))
650 changeImage(panel, i, 0, False, False);
651 else {
652 if (i == orig)
653 dim = True;
654 changeImage(panel, i, 0, True, False);
659 if (panel->current < panel->firstVisible)
660 scrollIcons(panel, panel->current - panel->firstVisible);
661 else if (panel->current - panel->firstVisible >= panel->visibleCount)
662 scrollIcons(panel, panel->current - panel->firstVisible - panel->visibleCount + 1);
664 if (panel->win) {
665 drawTitle(panel, panel->current, wwin->frame->title);
666 if (panel->current != orig)
667 changeImage(panel, orig, 0, dim, False);
668 changeImage(panel, panel->current, 1, False, False);
671 return wwin;
674 WWindow *wSwitchPanelSelectFirst(WSwitchPanel *panel, int back)
676 WWindow *wwin;
677 int count = WMGetArrayItemCount(panel->windows);
678 char *title;
679 int i;
681 if (count == 0)
682 return NULL;
684 if (back) {
685 panel->current = count - 1;
686 scrollIcons(panel, count);
687 } else {
688 panel->current = 0;
689 scrollIcons(panel, -count);
692 wwin = WMGetFromArray(panel->windows, panel->current);
693 title = wwin->frame->title;
695 if (panel->win) {
696 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
697 changeImage(panel, i, i == panel->current, False, False);
699 drawTitle(panel, panel->current, title);
702 return wwin;
705 WWindow *wSwitchPanelHandleEvent(WSwitchPanel *panel, XEvent *event)
707 WMFrame *icon;
708 int i;
709 int focus = -1;
711 if (!panel->win)
712 return NULL;
714 if (event->type == MotionNotify) {
715 WM_ITERATE_ARRAY(panel->icons, icon, i) {
716 if (WMWidgetXID(icon) == event->xmotion.window) {
717 focus = i;
718 break;
723 if (focus >= 0 && panel->current != focus) {
724 WWindow *wwin;
726 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
727 changeImage(panel, i, i == focus, False, False);
729 panel->current = focus;
731 wwin = WMGetFromArray(panel->windows, focus);
733 drawTitle(panel, panel->current, wwin->frame->title);
735 return wwin;
738 return NULL;
741 Window wSwitchPanelGetWindow(WSwitchPanel *swpanel)
743 if (!swpanel->win)
744 return None;
746 return WMWidgetXID(swpanel->win);