Added option to ignore minimized windows during cycling.
[wmaker-crm.git] / src / switchpanel.c
blobfcd61fa01690ec0e5cd6719148b64947b86f5fa5
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 "funcs.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 RImage *bg;
54 int current;
55 int firstVisible;
56 int visibleCount;
58 WMLabel *label;
60 RImage *tileTmp;
61 RImage *tile;
63 WMFont *font;
64 WMColor *white;
67 extern WPreferences wPreferences;
69 #define BORDER_SPACE 10
70 #define ICON_SIZE 48
71 #define ICON_TILE_SIZE 64
72 #define LABEL_HEIGHT 25
73 #define SCREEN_BORDER_SPACING 2*20
74 #define SCROLL_STEPS (ICON_TILE_SIZE/2)
76 static int canReceiveFocus(WWindow *wwin)
78 if (wwin->frame->workspace != wwin->screen_ptr->current_workspace)
79 return 0;
81 if (wPreferences.cycle_active_head_only &&
82 wGetHeadForWindow(wwin) != wGetHeadForPointerLocation(wwin->screen_ptr))
83 return 0;
85 if (WFLAGP(wwin, no_focusable))
86 return 0;
88 if (!wwin->flags.mapped) {
89 if (!wwin->flags.shaded && !wwin->flags.miniaturized && !wwin->flags.hidden)
90 return 0;
91 else
92 return -1;
95 return 1;
98 static void changeImage(WSwitchPanel *panel, int idecks, int selected)
100 WMFrame *icon = WMGetFromArray(panel->icons, idecks);
101 RImage *image = WMGetFromArray(panel->images, idecks);
103 if (!panel->bg && !panel->tile && !selected)
104 WMSetFrameRelief(icon, WRFlat);
106 if (image && icon) {
107 RImage *back;
108 int opaq = 255;
109 RImage *tile;
110 WMPoint pos;
111 Pixmap p;
113 if (canReceiveFocus(WMGetFromArray(panel->windows, idecks)) < 0)
114 opaq = 50;
116 pos = WMGetViewPosition(WMWidgetView(icon));
117 back = panel->tileTmp;
118 if (panel->bg) {
119 RCopyArea(back, panel->bg,
120 BORDER_SPACE + pos.x - panel->firstVisible * ICON_TILE_SIZE,
121 BORDER_SPACE + pos.y, back->width, back->height, 0, 0);
122 } else {
123 RColor color;
124 WMScreen *wscr = WMWidgetScreen(icon);
125 color.red = 255;
126 color.red = WMRedComponentOfColor(WMGrayColor(wscr)) >> 8;
127 color.green = WMGreenComponentOfColor(WMGrayColor(wscr)) >> 8;
128 color.blue = WMBlueComponentOfColor(WMGrayColor(wscr)) >> 8;
129 RFillImage(back, &color);
132 if (selected) {
133 tile = panel->tile;
134 RCombineArea(back, tile, 0, 0, tile->width, tile->height,
135 (back->width - tile->width) / 2, (back->height - tile->height) / 2);
138 RCombineAreaWithOpaqueness(back, image, 0, 0, image->width, image->height,
139 (back->width - image->width) / 2, (back->height - image->height) / 2,
140 opaq);
142 RConvertImage(panel->scr->rcontext, back, &p);
143 XSetWindowBackgroundPixmap(dpy, WMWidgetXID(icon), p);
144 XClearWindow(dpy, WMWidgetXID(icon));
145 XFreePixmap(dpy, p);
148 if (!panel->bg && !panel->tile && selected)
149 WMSetFrameRelief(icon, WRSimple);
152 static void addIconForWindow(WSwitchPanel *panel, WMWidget *parent, WWindow *wwin, int x, int y)
154 WMFrame *icon = WMCreateFrame(parent);
155 RImage *image = NULL;
157 WMSetFrameRelief(icon, WRFlat);
158 WMResizeWidget(icon, ICON_TILE_SIZE, ICON_TILE_SIZE);
159 WMMoveWidget(icon, x, y);
161 if (!WFLAGP(wwin, always_user_icon) && wwin->net_icon_image)
162 image = RRetainImage(wwin->net_icon_image);
164 /* get_icon_image() includes the default icon image */
165 if (!image)
166 image = get_icon_image(panel->scr, wwin->wm_instance, wwin->wm_class, ICON_TILE_SIZE);
168 /* We must resize the icon size (~64) to the switchpanel icon size (~48) */
169 image = wIconValidateIconSize(image, ICON_SIZE);
171 WMAddToArray(panel->images, image);
172 WMAddToArray(panel->icons, icon);
175 static void scrollIcons(WSwitchPanel *panel, int delta)
177 int nfirst = panel->firstVisible + delta;
178 int i;
179 int count = WMGetArrayItemCount(panel->windows);
181 if (count <= panel->visibleCount)
182 return;
184 if (nfirst < 0)
185 nfirst = 0;
186 else if (nfirst >= count - panel->visibleCount)
187 nfirst = count - panel->visibleCount;
189 if (nfirst == panel->firstVisible)
190 return;
192 WMMoveWidget(panel->iconBox, -nfirst * ICON_TILE_SIZE, 0);
194 panel->firstVisible = nfirst;
196 for (i = panel->firstVisible; i < panel->firstVisible + panel->visibleCount; i++)
197 changeImage(panel, i, i == panel->current);
201 * 0 1 2
202 * 3 4 5
203 * 6 7 8
205 static RImage *assemblePuzzleImage(RImage **images, int width, int height)
207 RImage *img = RCreateImage(width, height, 1);
208 RImage *tmp;
209 int tw, th;
210 RColor color;
211 if (!img)
212 return NULL;
214 color.red = 0;
215 color.green = 0;
216 color.blue = 0;
217 color.alpha = 255;
219 RFillImage(img, &color);
221 tw = width - images[0]->width - images[2]->width;
222 th = height - images[0]->height - images[6]->height;
224 if (tw <= 0 || th <= 0)
225 return NULL;
227 /* top */
228 if (tw > 0) {
229 tmp = RSmoothScaleImage(images[1], tw, images[1]->height);
230 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[0]->width, 0);
231 RReleaseImage(tmp);
233 /* bottom */
234 if (tw > 0) {
235 tmp = RSmoothScaleImage(images[7], tw, images[7]->height);
236 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[6]->width, height - images[6]->height);
237 RReleaseImage(tmp);
239 /* left */
240 if (th > 0) {
241 tmp = RSmoothScaleImage(images[3], images[3]->width, th);
242 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, 0, images[0]->height);
243 RReleaseImage(tmp);
245 /* right */
246 if (th > 0) {
247 tmp = RSmoothScaleImage(images[5], images[5]->width, th);
248 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, width - images[5]->width, images[2]->height);
249 RReleaseImage(tmp);
251 /* center */
252 if (tw > 0 && th > 0) {
253 tmp = RSmoothScaleImage(images[4], tw, th);
254 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[0]->width, images[0]->height);
255 RReleaseImage(tmp);
258 /* corners */
259 RCopyArea(img, images[0], 0, 0, images[0]->width, images[0]->height, 0, 0);
260 RCopyArea(img, images[2], 0, 0, images[2]->width, images[2]->height, width - images[2]->width, 0);
261 RCopyArea(img, images[6], 0, 0, images[6]->width, images[6]->height, 0, height - images[6]->height);
262 RCopyArea(img, images[8], 0, 0, images[8]->width, images[8]->height,
263 width - images[8]->width, height - images[8]->height);
265 return img;
268 static RImage *createBackImage(int width, int height)
270 return assemblePuzzleImage(wPreferences.swbackImage, width, height);
273 static RImage *getTile(void)
275 RImage *stile;
277 if (!wPreferences.swtileImage)
278 return NULL;
280 stile = RScaleImage(wPreferences.swtileImage, ICON_TILE_SIZE, ICON_TILE_SIZE);
281 if (!stile)
282 return wPreferences.swtileImage;
284 return stile;
287 static void drawTitle(WSwitchPanel *panel, int idecks, char *title)
289 char *ntitle;
290 int width = WMWidgetWidth(panel->win);
291 int x;
293 if (title)
294 ntitle = ShrinkString(panel->font, title, width - 2 * BORDER_SPACE);
295 else
296 ntitle = NULL;
298 if (panel->bg) {
299 if (ntitle) {
300 if (strcmp(ntitle, title) != 0) {
301 x = BORDER_SPACE;
302 } else {
303 int w = WMWidthOfString(panel->font, ntitle, strlen(ntitle));
305 x = BORDER_SPACE + (idecks - panel->firstVisible) * ICON_TILE_SIZE +
306 ICON_TILE_SIZE / 2 - w / 2;
307 if (x < BORDER_SPACE)
308 x = BORDER_SPACE;
309 else if (x + w > width - BORDER_SPACE)
310 x = width - BORDER_SPACE - w;
314 XClearWindow(dpy, WMWidgetXID(panel->win));
315 if (ntitle)
316 WMDrawString(panel->scr->wmscreen,
317 WMWidgetXID(panel->win),
318 panel->white, panel->font,
320 WMWidgetHeight(panel->win) - BORDER_SPACE - LABEL_HEIGHT +
321 WMFontHeight(panel->font) / 2, ntitle, strlen(ntitle));
322 } else {
323 if (ntitle)
324 WMSetLabelText(panel->label, ntitle);
327 if (ntitle)
328 free(ntitle);
331 static WMArray *makeWindowListArray(WWindow *curwin, int include_unmapped, Bool class_only)
333 WMArray *windows = WMCreateArray(10);
334 int fl;
335 WWindow *wwin;
337 for (fl = 0; fl < 2; fl++) {
338 for (wwin = curwin; wwin; wwin = wwin->prev) {
339 if (((!fl && canReceiveFocus(wwin) > 0) || (fl && canReceiveFocus(wwin) < 0)) &&
340 (wwin->flags.mapped || include_unmapped)) {
341 if (class_only) {
342 if (!wwin->wm_class || !curwin->wm_class)
343 continue;
344 if (strcmp(wwin->wm_class, curwin->wm_class))
345 continue;
348 if (!WFLAGP(wwin, skip_switchpanel))
349 WMAddToArray(windows, wwin);
352 wwin = curwin;
353 /* start over from the beginning of the list */
354 while (wwin->next)
355 wwin = wwin->next;
357 for (wwin = curwin; wwin && wwin != curwin; wwin = wwin->prev) {
358 if (((!fl && canReceiveFocus(wwin) > 0) || (fl && canReceiveFocus(wwin) < 0)) &&
359 (wwin->flags.mapped || include_unmapped)) {
360 if (class_only) {
361 if (!wwin->wm_class || !curwin->wm_class)
362 continue;
363 if (strcmp(wwin->wm_class, curwin->wm_class))
364 continue;
367 if (!WFLAGP(wwin, skip_switchpanel))
368 WMAddToArray(windows, wwin);
373 return windows;
376 WSwitchPanel *wInitSwitchPanel(WScreen *scr, WWindow *curwin, Bool class_only)
378 WWindow *wwin;
379 WSwitchPanel *panel = wmalloc(sizeof(WSwitchPanel));
380 WMFrame *viewport;
381 int i, width, height, iconsThatFitCount, count;
382 WMRect rect = wGetRectForHead(scr, wGetHeadForPointerLocation(scr));
384 panel->scr = scr;
385 panel->windows = makeWindowListArray(curwin, wPreferences.swtileImage != NULL, class_only);
386 count = WMGetArrayItemCount(panel->windows);
388 if (count == 0) {
389 WMFreeArray(panel->windows);
390 wfree(panel);
391 return NULL;
394 width = ICON_TILE_SIZE * count;
395 iconsThatFitCount = count;
397 if (width > rect.size.width) {
398 iconsThatFitCount = (rect.size.width - SCREEN_BORDER_SPACING) / ICON_TILE_SIZE;
399 width = iconsThatFitCount * ICON_TILE_SIZE;
402 panel->visibleCount = iconsThatFitCount;
404 if (!wPreferences.swtileImage)
405 return panel;
407 height = LABEL_HEIGHT + ICON_TILE_SIZE;
409 panel->tileTmp = RCreateImage(ICON_TILE_SIZE, ICON_TILE_SIZE, 1);
410 panel->tile = getTile();
411 if (panel->tile && wPreferences.swbackImage[8])
412 panel->bg = createBackImage(width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
414 if (!panel->tileTmp || !panel->tile) {
415 if (panel->bg)
416 RReleaseImage(panel->bg);
417 panel->bg = NULL;
418 if (panel->tile)
419 RReleaseImage(panel->tile);
420 panel->tile = NULL;
421 if (panel->tileTmp)
422 RReleaseImage(panel->tileTmp);
423 panel->tileTmp = NULL;
426 panel->white = WMWhiteColor(scr->wmscreen);
427 panel->font = WMBoldSystemFontOfSize(scr->wmscreen, 12);
428 panel->icons = WMCreateArray(count);
429 panel->images = WMCreateArray(count);
431 panel->win = WMCreateWindow(scr->wmscreen, "");
433 if (!panel->bg) {
434 WMFrame *frame = WMCreateFrame(panel->win);
435 WMColor *darkGray = WMDarkGrayColor(scr->wmscreen);
436 WMSetFrameRelief(frame, WRSimple);
437 WMSetViewExpandsToParent(WMWidgetView(frame), 0, 0, 0, 0);
439 panel->label = WMCreateLabel(panel->win);
440 WMResizeWidget(panel->label, width, LABEL_HEIGHT);
441 WMMoveWidget(panel->label, BORDER_SPACE, BORDER_SPACE + ICON_TILE_SIZE + 5);
442 WMSetLabelRelief(panel->label, WRSimple);
443 WMSetWidgetBackgroundColor(panel->label, darkGray);
444 WMSetLabelFont(panel->label, panel->font);
445 WMSetLabelTextColor(panel->label, panel->white);
447 WMReleaseColor(darkGray);
448 height += 5;
451 WMResizeWidget(panel->win, width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
453 viewport = WMCreateFrame(panel->win);
454 WMResizeWidget(viewport, width, ICON_TILE_SIZE);
455 WMMoveWidget(viewport, BORDER_SPACE, BORDER_SPACE);
456 WMSetFrameRelief(viewport, WRFlat);
458 panel->iconBox = WMCreateFrame(viewport);
459 WMMoveWidget(panel->iconBox, 0, 0);
460 WMResizeWidget(panel->iconBox, ICON_TILE_SIZE * count, ICON_TILE_SIZE);
461 WMSetFrameRelief(panel->iconBox, WRFlat);
463 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
464 addIconForWindow(panel, panel->iconBox, wwin, i * ICON_TILE_SIZE, 0);
467 WMMapSubwidgets(panel->win);
468 WMRealizeWidget(panel->win);
470 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
471 changeImage(panel, i, 0);
474 if (panel->bg) {
475 Pixmap pixmap, mask;
477 RConvertImageMask(scr->rcontext, panel->bg, &pixmap, &mask, 250);
479 XSetWindowBackgroundPixmap(dpy, WMWidgetXID(panel->win), pixmap);
481 #ifdef SHAPE
482 if (mask && wShapeSupported)
483 XShapeCombineMask(dpy, WMWidgetXID(panel->win), ShapeBounding, 0, 0, mask, ShapeSet);
484 #endif
485 if (pixmap)
486 XFreePixmap(dpy, pixmap);
488 if (mask)
489 XFreePixmap(dpy, mask);
493 WMPoint center;
494 center = wGetPointToCenterRectInHead(scr, wGetHeadForPointerLocation(scr),
495 width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
496 WMMoveWidget(panel->win, center.x, center.y);
499 panel->current = WMGetFirstInArray(panel->windows, curwin);
500 if (panel->current >= 0)
501 changeImage(panel, panel->current, 1);
503 WMMapWidget(panel->win);
505 return panel;
508 void wSwitchPanelDestroy(WSwitchPanel *panel)
510 int i;
511 RImage *image;
513 if (panel->win) {
514 Window info_win = panel->scr->info_window;
515 XEvent ev;
516 ev.xclient.type = ClientMessage;
517 ev.xclient.message_type = _XA_WM_IGNORE_FOCUS_EVENTS;
518 ev.xclient.format = 32;
519 ev.xclient.data.l[0] = True;
521 XSendEvent(dpy, info_win, True, EnterWindowMask, &ev);
522 WMUnmapWidget(panel->win);
524 ev.xclient.data.l[0] = False;
525 XSendEvent(dpy, info_win, True, EnterWindowMask, &ev);
528 if (panel->images) {
529 WM_ITERATE_ARRAY(panel->images, image, i) {
530 if (image)
531 RReleaseImage(image);
533 WMFreeArray(panel->images);
536 if (panel->win)
537 WMDestroyWidget(panel->win);
539 if (panel->icons)
540 WMFreeArray(panel->icons);
542 WMFreeArray(panel->windows);
544 if (panel->tile)
545 RReleaseImage(panel->tile);
547 if (panel->tileTmp)
548 RReleaseImage(panel->tileTmp);
550 if (panel->bg)
551 RReleaseImage(panel->bg);
553 if (panel->font)
554 WMReleaseFont(panel->font);
556 if (panel->white)
557 WMReleaseColor(panel->white);
559 wfree(panel);
562 WWindow *wSwitchPanelSelectNext(WSwitchPanel *panel, int back, int ignore_minimized)
564 WWindow *wwin;
565 int count = WMGetArrayItemCount(panel->windows);
566 int orig = panel->current;
568 if (count == 0)
569 return NULL;
571 if (panel->win)
572 changeImage(panel, panel->current, 0);
574 if (!wPreferences.cycle_ignore_minimized)
575 ignore_minimized = False;
577 if (ignore_minimized && canReceiveFocus(WMGetFromArray(panel->windows, (count + panel->current) % count)) < 0)
578 ignore_minimized = False;
580 do {
581 if (back)
582 panel->current--;
583 else
584 panel->current++;
586 panel->current= (count + panel->current) % count;
587 wwin = WMGetFromArray(panel->windows, panel->current);
588 } while (ignore_minimized && panel->current != orig && canReceiveFocus(wwin) < 0);
590 if (panel->current < panel->firstVisible)
591 scrollIcons(panel, panel->current - panel->firstVisible);
592 else if (panel->current - panel->firstVisible >= panel->visibleCount)
593 scrollIcons(panel, panel->current - panel->firstVisible - panel->visibleCount + 1);
595 if (panel->win) {
596 drawTitle(panel, panel->current, wwin->frame->title);
598 changeImage(panel, panel->current, 1);
601 return wwin;
604 WWindow *wSwitchPanelSelectFirst(WSwitchPanel *panel, int back)
606 WWindow *wwin;
607 int count = WMGetArrayItemCount(panel->windows);
609 if (count == 0)
610 return NULL;
612 if (panel->win)
613 changeImage(panel, panel->current, 0);
615 if (back) {
616 panel->current = count - 1;
617 scrollIcons(panel, count);
618 } else {
619 panel->current = 0;
620 scrollIcons(panel, -count);
623 wwin = WMGetFromArray(panel->windows, panel->current);
625 if (panel->win) {
626 drawTitle(panel, panel->current, wwin->frame->title);
627 changeImage(panel, panel->current, 1);
630 return wwin;
633 WWindow *wSwitchPanelHandleEvent(WSwitchPanel *panel, XEvent *event)
635 WMFrame *icon;
636 int i;
637 int focus = -1;
639 if (!panel->win)
640 return NULL;
642 if (event->type == MotionNotify) {
643 WM_ITERATE_ARRAY(panel->icons, icon, i) {
644 if (WMWidgetXID(icon) == event->xmotion.window) {
645 focus = i;
646 break;
651 if (focus >= 0 && panel->current != focus) {
652 WWindow *wwin;
654 changeImage(panel, panel->current, 0);
655 changeImage(panel, focus, 1);
656 panel->current = focus;
658 wwin = WMGetFromArray(panel->windows, focus);
660 drawTitle(panel, panel->current, wwin->frame->title);
662 return wwin;
665 return NULL;
668 Window wSwitchPanelGetWindow(WSwitchPanel *swpanel)
670 if (!swpanel->win)
671 return None;
673 return WMWidgetXID(swpanel->win);