Steal documentation with tips on email clients
[wmaker-crm.git] / src / switchpanel.c
blob3836991b9b7fad43260989f463e1e65a4635237b
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 "window.h"
31 #include "defaults.h"
32 #include "switchpanel.h"
33 #include "funcs.h"
34 #include "xinerama.h"
36 extern Atom _XA_WM_IGNORE_FOCUS_EVENTS;
38 #ifdef SHAPE
39 #include <X11/extensions/shape.h>
41 extern Bool wShapeSupported;
42 #endif
44 struct SwitchPanel {
45 WScreen *scr;
46 WMWindow *win;
47 WMFrame *iconBox;
49 WMArray *icons;
50 WMArray *images;
51 WMArray *windows;
52 RImage *bg;
53 int current;
54 int firstVisible;
55 int visibleCount;
57 WMLabel *label;
59 RImage *defIcon;
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 static int canReceiveFocus(WWindow * wwin)
79 if (wwin->frame->workspace != wwin->screen_ptr->current_workspace)
80 return 0;
82 if (wPreferences.cycle_active_head_only &&
83 wGetHeadForWindow(wwin) != wGetHeadForPointerLocation(wwin->screen_ptr))
84 return 0;
86 if (!wwin->flags.mapped) {
87 if (!wwin->flags.shaded && !wwin->flags.miniaturized && !wwin->flags.hidden)
88 return 0;
89 else
90 return -1;
92 if (WFLAGP(wwin, no_focusable))
93 return 0;
94 return 1;
97 static void changeImage(WSwitchPanel * panel, int idecks, int selected)
99 WMFrame *icon = WMGetFromArray(panel->icons, idecks);
100 RImage *image = WMGetFromArray(panel->images, idecks);
102 if (!panel->bg && !panel->tile) {
103 if (!selected)
104 WMSetFrameRelief(icon, WRFlat);
107 if (image && icon) {
108 RImage *back;
109 int opaq = 255;
110 RImage *tile;
111 WMPoint pos;
112 Pixmap p;
114 if (canReceiveFocus(WMGetFromArray(panel->windows, idecks)) < 0)
115 opaq = 50;
117 pos = WMGetViewPosition(WMWidgetView(icon));
118 back = panel->tileTmp;
119 if (panel->bg)
120 RCopyArea(back, panel->bg,
121 BORDER_SPACE + pos.x - panel->firstVisible * ICON_TILE_SIZE,
122 BORDER_SPACE + pos.y, back->width, back->height, 0, 0);
123 else {
124 RColor color;
125 WMScreen *wscr = WMWidgetScreen(icon);
126 color.red = 255;
127 color.red = WMRedComponentOfColor(WMGrayColor(wscr)) >> 8;
128 color.green = WMGreenComponentOfColor(WMGrayColor(wscr)) >> 8;
129 color.blue = WMBlueComponentOfColor(WMGrayColor(wscr)) >> 8;
130 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);
137 RCombineAreaWithOpaqueness(back, image, 0, 0, image->width, image->height,
138 (back->width - image->width) / 2, (back->height - image->height) / 2,
139 opaq);
141 RConvertImage(panel->scr->rcontext, back, &p);
142 XSetWindowBackgroundPixmap(dpy, WMWidgetXID(icon), p);
143 XClearWindow(dpy, WMWidgetXID(icon));
144 XFreePixmap(dpy, p);
147 if (!panel->bg && !panel->tile) {
148 if (selected)
149 WMSetFrameRelief(icon, WRSimple);
153 static RImage *scaleDownIfNeeded(RImage * image)
155 if (image && ((image->width - ICON_SIZE) > 2 || (image->height - ICON_SIZE) > 2)) {
156 RImage *nimage;
157 nimage = RScaleImage(image, ICON_SIZE, (image->height * ICON_SIZE / image->width));
158 RReleaseImage(image);
159 image = nimage;
161 return image;
164 static void addIconForWindow(WSwitchPanel * panel, WMWidget * parent, WWindow * wwin, int x, int y)
166 WMFrame *icon = WMCreateFrame(parent);
167 RImage *image = NULL;
169 WMSetFrameRelief(icon, WRFlat);
170 WMResizeWidget(icon, ICON_TILE_SIZE, ICON_TILE_SIZE);
171 WMMoveWidget(icon, x, y);
173 if (!WFLAGP(wwin, always_user_icon) && wwin->net_icon_image)
174 image = RRetainImage(wwin->net_icon_image);
176 // Make this use a caching thing. When there are many windows,
177 // it's very likely that most of them are instances of the same thing,
178 // so caching them should get performance acceptable in these cases.
179 if (!image)
180 image = wDefaultGetImage(panel->scr, wwin->wm_instance, wwin->wm_class);
182 if (!image && !panel->defIcon) {
183 char *file = wDefaultGetIconFile(panel->scr, NULL, NULL, False);
184 if (file) {
185 char *path = FindImage(wPreferences.icon_path, file);
186 if (path) {
187 image = RLoadImage(panel->scr->rcontext, path, 0);
188 wfree(path);
191 if (image)
192 panel->defIcon = scaleDownIfNeeded(image);
193 image = NULL;
195 if (!image && panel->defIcon)
196 image = RRetainImage(panel->defIcon);
198 image = scaleDownIfNeeded(image);
200 WMAddToArray(panel->images, image);
201 WMAddToArray(panel->icons, icon);
204 static void scrollIcons(WSwitchPanel * panel, int delta)
206 int nfirst = panel->firstVisible + delta;
207 int i;
208 int count = WMGetArrayItemCount(panel->windows);
209 // int nx, ox;
210 // struct timeval tv1, tv2;
212 if (count <= panel->visibleCount)
213 return;
215 if (nfirst < 0)
216 nfirst = 0;
217 else if (nfirst >= count - panel->visibleCount)
218 nfirst = count - panel->visibleCount;
220 if (nfirst == panel->firstVisible)
221 return;
223 ox = -panel->firstVisible * ICON_TILE_SIZE;
224 nx = -nfirst * ICON_TILE_SIZE;
225 for (i= 0; i < SCROLL_STEPS; i++) {
226 unsigned int diff;
227 gettimeofday(&tv1, NULL);
228 WMMoveWidget(panel->iconBox, (nx*i + ox*(SCROLL_STEPS-i))/(SCROLL_STEPS-1), 0);
229 XSync(dpy, False);
230 gettimeofday(&tv2, NULL);
231 diff = (tv2.tv_sec-tv1.tv_sec)*10000+(tv2.tv_usec-tv1.tv_usec)/100;
232 if (diff < 200)
233 wusleep(300-diff);
236 WMMoveWidget(panel->iconBox, -nfirst * ICON_TILE_SIZE, 0);
238 panel->firstVisible = nfirst;
240 for (i = panel->firstVisible; i < panel->firstVisible + panel->visibleCount; i++) {
241 changeImage(panel, i, i == panel->current);
246 * 0 1 2
247 * 3 4 5
248 * 6 7 8
250 static RImage *assemblePuzzleImage(RImage ** images, int width, int height)
252 RImage *img = RCreateImage(width, height, 1);
253 RImage *tmp;
254 int tw, th;
255 RColor color;
256 if (!img)
257 return NULL;
259 color.red = 0;
260 color.green = 0;
261 color.blue = 0;
262 color.alpha = 255;
264 RFillImage(img, &color);
266 tw = width - images[0]->width - images[2]->width;
267 th = height - images[0]->height - images[6]->height;
269 if (tw <= 0 || th <= 0) {
270 //XXX
271 return NULL;
274 /* top */
275 if (tw > 0) {
276 tmp = RSmoothScaleImage(images[1], tw, images[1]->height);
277 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[0]->width, 0);
278 RReleaseImage(tmp);
280 /* bottom */
281 if (tw > 0) {
282 tmp = RSmoothScaleImage(images[7], tw, images[7]->height);
283 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[6]->width, height - images[6]->height);
284 RReleaseImage(tmp);
286 /* left */
287 if (th > 0) {
288 tmp = RSmoothScaleImage(images[3], images[3]->width, th);
289 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, 0, images[0]->height);
290 RReleaseImage(tmp);
292 /* right */
293 if (th > 0) {
294 tmp = RSmoothScaleImage(images[5], images[5]->width, th);
295 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, width - images[5]->width, images[2]->height);
296 RReleaseImage(tmp);
298 /* center */
299 if (tw > 0 && th > 0) {
300 tmp = RSmoothScaleImage(images[4], tw, th);
301 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[0]->width, images[0]->height);
302 RReleaseImage(tmp);
305 /* corners */
306 RCopyArea(img, images[0], 0, 0, images[0]->width, images[0]->height, 0, 0);
308 RCopyArea(img, images[2], 0, 0, images[2]->width, images[2]->height, width - images[2]->width, 0);
310 RCopyArea(img, images[6], 0, 0, images[6]->width, images[6]->height, 0, height - images[6]->height);
312 RCopyArea(img, images[8], 0, 0, images[8]->width, images[8]->height,
313 width - images[8]->width, height - images[8]->height);
315 return img;
318 static RImage *createBackImage(int width, int height)
320 return assemblePuzzleImage(wPreferences.swbackImage, width, height);
323 static RImage *getTile(void)
325 RImage *stile;
327 if (!wPreferences.swtileImage)
328 return NULL;
330 stile = RScaleImage(wPreferences.swtileImage, ICON_TILE_SIZE, ICON_TILE_SIZE);
331 if (!stile)
332 return wPreferences.swtileImage;
334 return stile;
337 static void drawTitle(WSwitchPanel * panel, int idecks, char *title)
339 char *ntitle;
340 int width = WMWidgetWidth(panel->win);
341 int x;
343 if (title)
344 ntitle = ShrinkString(panel->font, title, width - 2 * BORDER_SPACE);
345 else
346 ntitle = NULL;
348 if (panel->bg) {
349 if (ntitle) {
350 if (strcmp(ntitle, title) != 0)
351 x = BORDER_SPACE;
352 else {
353 int w = WMWidthOfString(panel->font, ntitle, strlen(ntitle));
355 x = BORDER_SPACE + (idecks - panel->firstVisible) * ICON_TILE_SIZE +
356 ICON_TILE_SIZE / 2 - w / 2;
357 if (x < BORDER_SPACE)
358 x = BORDER_SPACE;
359 else if (x + w > width - BORDER_SPACE)
360 x = width - BORDER_SPACE - w;
363 XClearWindow(dpy, WMWidgetXID(panel->win));
364 if (ntitle)
365 WMDrawString(panel->scr->wmscreen,
366 WMWidgetXID(panel->win),
367 panel->white, panel->font,
369 WMWidgetHeight(panel->win) - BORDER_SPACE - LABEL_HEIGHT +
370 WMFontHeight(panel->font) / 2, ntitle, strlen(ntitle));
371 } else {
372 if (ntitle)
373 WMSetLabelText(panel->label, ntitle);
375 if (ntitle)
376 free(ntitle);
379 static WMArray *makeWindowListArray(WWindow *curwin, int include_unmapped, Bool class_only)
381 WMArray *windows = WMCreateArray(10);
382 int fl;
383 WWindow *wwin;
385 for (fl = 0; fl < 2; fl++) {
386 for (wwin = curwin; wwin; wwin = wwin->prev) {
387 if (((!fl && canReceiveFocus(wwin) > 0) || (fl && canReceiveFocus(wwin) < 0)) &&
388 (wwin->flags.mapped || include_unmapped)) {
389 if (class_only) {
390 if (!wwin->wm_class || !curwin->wm_class)
391 continue;
392 if (strcmp(wwin->wm_class, curwin->wm_class))
393 continue;
396 if (!WFLAGP(wwin, skip_switchpanel))
397 WMAddToArray(windows, wwin);
400 wwin = curwin;
401 /* start over from the beginning of the list */
402 while (wwin->next)
403 wwin = wwin->next;
405 for (wwin = curwin; wwin && wwin != curwin; wwin = wwin->prev) {
406 if (((!fl && canReceiveFocus(wwin) > 0) || (fl && canReceiveFocus(wwin) < 0)) &&
407 (wwin->flags.mapped || include_unmapped)) {
408 if (class_only) {
409 if (!wwin->wm_class || !curwin->wm_class)
410 continue;
411 if (strcmp(wwin->wm_class, curwin->wm_class))
412 continue;
415 if (!WFLAGP(wwin, skip_switchpanel))
416 WMAddToArray(windows, wwin);
421 return windows;
424 WSwitchPanel *wInitSwitchPanel(WScreen * scr, WWindow * curwin, Bool class_only)
426 WWindow *wwin;
427 WSwitchPanel *panel = wmalloc(sizeof(WSwitchPanel));
428 WMFrame *viewport;
429 int i;
430 int width, height;
431 int iconsThatFitCount;
432 int count;
433 WMRect rect;
434 rect = wGetRectForHead(scr, wGetHeadForPointerLocation(scr));
436 memset(panel, 0, sizeof(WSwitchPanel));
438 panel->scr = scr;
440 panel->windows = makeWindowListArray(curwin, wPreferences.swtileImage != NULL, class_only);
441 count = WMGetArrayItemCount(panel->windows);
443 if (count == 0) {
444 WMFreeArray(panel->windows);
445 wfree(panel);
446 return NULL;
449 width = ICON_TILE_SIZE * count;
450 iconsThatFitCount = count;
452 if (width > rect.size.width) {
453 iconsThatFitCount = (rect.size.width - SCREEN_BORDER_SPACING) / ICON_TILE_SIZE;
454 width = iconsThatFitCount * ICON_TILE_SIZE;
457 panel->visibleCount = iconsThatFitCount;
459 if (!wPreferences.swtileImage)
460 return panel;
462 height = LABEL_HEIGHT + ICON_TILE_SIZE;
464 panel->tileTmp = RCreateImage(ICON_TILE_SIZE, ICON_TILE_SIZE, 1);
465 panel->tile = getTile();
466 if (panel->tile && wPreferences.swbackImage[8]) {
467 panel->bg = createBackImage(width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
469 if (!panel->tileTmp || !panel->tile) {
470 if (panel->bg)
471 RReleaseImage(panel->bg);
472 panel->bg = NULL;
473 if (panel->tile)
474 RReleaseImage(panel->tile);
475 panel->tile = NULL;
476 if (panel->tileTmp)
477 RReleaseImage(panel->tileTmp);
478 panel->tileTmp = NULL;
481 panel->white = WMWhiteColor(scr->wmscreen);
482 panel->font = WMBoldSystemFontOfSize(scr->wmscreen, 12);
483 panel->icons = WMCreateArray(count);
484 panel->images = WMCreateArray(count);
486 panel->win = WMCreateWindow(scr->wmscreen, "");
488 if (!panel->bg) {
489 WMFrame *frame = WMCreateFrame(panel->win);
490 WMColor *darkGray = WMDarkGrayColor(scr->wmscreen);
491 WMSetFrameRelief(frame, WRSimple);
492 WMSetViewExpandsToParent(WMWidgetView(frame), 0, 0, 0, 0);
494 panel->label = WMCreateLabel(panel->win);
495 WMResizeWidget(panel->label, width, LABEL_HEIGHT);
496 WMMoveWidget(panel->label, BORDER_SPACE, BORDER_SPACE + ICON_TILE_SIZE + 5);
497 WMSetLabelRelief(panel->label, WRSimple);
498 WMSetWidgetBackgroundColor(panel->label, darkGray);
499 WMSetLabelFont(panel->label, panel->font);
500 WMSetLabelTextColor(panel->label, panel->white);
502 WMReleaseColor(darkGray);
503 height += 5;
506 WMResizeWidget(panel->win, width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
508 viewport = WMCreateFrame(panel->win);
509 WMResizeWidget(viewport, width, ICON_TILE_SIZE);
510 WMMoveWidget(viewport, BORDER_SPACE, BORDER_SPACE);
511 WMSetFrameRelief(viewport, WRFlat);
513 panel->iconBox = WMCreateFrame(viewport);
514 WMMoveWidget(panel->iconBox, 0, 0);
515 WMResizeWidget(panel->iconBox, ICON_TILE_SIZE * count, ICON_TILE_SIZE);
516 WMSetFrameRelief(panel->iconBox, WRFlat);
518 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
519 addIconForWindow(panel, panel->iconBox, wwin, i * ICON_TILE_SIZE, 0);
522 WMMapSubwidgets(panel->win);
523 WMRealizeWidget(panel->win);
525 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
526 changeImage(panel, i, 0);
529 if (panel->bg) {
530 Pixmap pixmap, mask;
532 RConvertImageMask(scr->rcontext, panel->bg, &pixmap, &mask, 250);
534 XSetWindowBackgroundPixmap(dpy, WMWidgetXID(panel->win), pixmap);
536 #ifdef SHAPE
537 if (mask && wShapeSupported)
538 XShapeCombineMask(dpy, WMWidgetXID(panel->win), ShapeBounding, 0, 0, mask, ShapeSet);
539 #endif
541 if (pixmap)
542 XFreePixmap(dpy, pixmap);
543 if (mask)
544 XFreePixmap(dpy, mask);
548 WMPoint center;
549 center = wGetPointToCenterRectInHead(scr, wGetHeadForPointerLocation(scr),
550 width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
551 WMMoveWidget(panel->win, center.x, center.y);
554 panel->current = WMGetFirstInArray(panel->windows, curwin);
555 if (panel->current >= 0)
556 changeImage(panel, panel->current, 1);
558 WMMapWidget(panel->win);
560 return panel;
563 void wSwitchPanelDestroy(WSwitchPanel * panel)
565 int i;
566 RImage *image;
568 if (panel->win) {
569 Window info_win = panel->scr->info_window;
570 XEvent ev;
571 ev.xclient.type = ClientMessage;
572 ev.xclient.message_type = _XA_WM_IGNORE_FOCUS_EVENTS;
573 ev.xclient.format = 32;
574 ev.xclient.data.l[0] = True;
576 XSendEvent(dpy, info_win, True, EnterWindowMask, &ev);
577 WMUnmapWidget(panel->win);
579 ev.xclient.data.l[0] = False;
580 XSendEvent(dpy, info_win, True, EnterWindowMask, &ev);
583 if (panel->images) {
584 WM_ITERATE_ARRAY(panel->images, image, i) {
585 if (image)
586 RReleaseImage(image);
588 WMFreeArray(panel->images);
590 if (panel->win)
591 WMDestroyWidget(panel->win);
592 if (panel->icons)
593 WMFreeArray(panel->icons);
594 WMFreeArray(panel->windows);
595 if (panel->defIcon)
596 RReleaseImage(panel->defIcon);
597 if (panel->tile)
598 RReleaseImage(panel->tile);
599 if (panel->tileTmp)
600 RReleaseImage(panel->tileTmp);
601 if (panel->bg)
602 RReleaseImage(panel->bg);
603 if (panel->font)
604 WMReleaseFont(panel->font);
605 if (panel->white)
606 WMReleaseColor(panel->white);
607 wfree(panel);
610 WWindow *wSwitchPanelSelectNext(WSwitchPanel * panel, int back)
612 WWindow *wwin;
613 int count = WMGetArrayItemCount(panel->windows);
615 if (count == 0)
616 return NULL;
618 if (panel->win)
619 changeImage(panel, panel->current, 0);
621 if (back)
622 panel->current--;
623 else
624 panel->current++;
626 wwin = WMGetFromArray(panel->windows, (count + panel->current) % count);
628 if (back) {
629 if (panel->current < 0)
630 scrollIcons(panel, count);
631 else if (panel->current < panel->firstVisible)
632 scrollIcons(panel, -1);
633 } else {
634 if (panel->current >= count)
635 scrollIcons(panel, -count);
636 else if (panel->current - panel->firstVisible >= panel->visibleCount)
637 scrollIcons(panel, 1);
640 panel->current = (count + panel->current) % count;
642 if (panel->win) {
643 drawTitle(panel, panel->current, wwin->frame->title);
645 changeImage(panel, panel->current, 1);
648 return wwin;
651 WWindow *wSwitchPanelSelectFirst(WSwitchPanel * panel, int back)
653 WWindow *wwin;
654 int count = WMGetArrayItemCount(panel->windows);
656 if (count == 0)
657 return NULL;
659 if (panel->win)
660 changeImage(panel, panel->current, 0);
662 if (back) {
663 panel->current = count - 1;
664 scrollIcons(panel, count);
665 } else {
666 panel->current = 0;
667 scrollIcons(panel, -count);
670 wwin = WMGetFromArray(panel->windows, panel->current);
672 if (panel->win) {
673 drawTitle(panel, panel->current, wwin->frame->title);
675 changeImage(panel, panel->current, 1);
677 return wwin;
680 WWindow *wSwitchPanelHandleEvent(WSwitchPanel * panel, XEvent * event)
682 WMFrame *icon;
683 int i;
684 int focus = -1;
686 if (!panel->win)
687 return NULL;
689 /* if (event->type == LeaveNotify) {
690 if (event->xcrossing.window == WMWidgetXID(panel->win))
691 focus= 0;
692 } else */ if (event->type == MotionNotify) {
694 WM_ITERATE_ARRAY(panel->icons, icon, i) {
695 if (WMWidgetXID(icon) == event->xmotion.window) {
696 focus = i;
697 break;
701 if (focus >= 0 && panel->current != focus) {
702 WWindow *wwin;
704 changeImage(panel, panel->current, 0);
705 changeImage(panel, focus, 1);
706 panel->current = focus;
708 wwin = WMGetFromArray(panel->windows, focus);
710 drawTitle(panel, panel->current, wwin->frame->title);
712 return wwin;
715 return NULL;
718 Window wSwitchPanelGetWindow(WSwitchPanel * swpanel)
720 if (!swpanel->win)
721 return None;
722 return WMWidgetXID(swpanel->win);