Fix focus issues with the switch panel and auto-focus
[wmaker-crm.git] / src / switchpanel.c
blob3e1ae158a117d2c59a0c9509b2515121f38fc05c
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
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 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/time.h>
28 #include "WindowMaker.h"
29 #include "screen.h"
30 #include "wcore.h"
31 #include "framewin.h"
32 #include "window.h"
33 #include "defaults.h"
34 #include "switchpanel.h"
35 #include "funcs.h"
36 #include "xinerama.h"
37 #include "window.h"
39 extern Atom _XA_WM_IGNORE_FOCUS_EVENTS;
41 #ifdef SHAPE
42 #include <X11/extensions/shape.h>
44 extern Bool wShapeSupported;
45 #endif
47 struct SwitchPanel {
48 WScreen *scr;
49 WMWindow *win;
50 WMFrame *iconBox;
52 WMArray *icons;
53 WMArray *images;
54 WMArray *windows;
55 RImage *bg;
56 int current;
57 int firstVisible;
58 int visibleCount;
60 WMLabel *label;
62 RImage *defIcon;
64 RImage *tileTmp;
65 RImage *tile;
67 WMFont *font;
68 WMColor *white;
71 extern WPreferences wPreferences;
73 #define BORDER_SPACE 10
74 #define ICON_SIZE 48
75 #define ICON_TILE_SIZE 64
76 #define LABEL_HEIGHT 25
77 #define SCREEN_BORDER_SPACING 2*20
78 #define SCROLL_STEPS (ICON_TILE_SIZE/2)
80 static int canReceiveFocus(WWindow * wwin)
82 if (wwin->frame->workspace != wwin->screen_ptr->current_workspace)
83 return 0;
84 if (!wwin->flags.mapped) {
85 if (!wwin->flags.shaded && !wwin->flags.miniaturized && !wwin->flags.hidden)
86 return 0;
87 else
88 return -1;
90 if (WFLAGP(wwin, no_focusable))
91 return 0;
92 return 1;
95 static void changeImage(WSwitchPanel * panel, int idecks, int selected)
97 WMFrame *icon = WMGetFromArray(panel->icons, idecks);
98 RImage *image = WMGetFromArray(panel->images, idecks);
100 if (!panel->bg && !panel->tile) {
101 if (!selected)
102 WMSetFrameRelief(icon, WRFlat);
105 if (image && icon) {
106 RImage *back;
107 int opaq = 255;
108 RImage *tile;
109 WMPoint pos;
110 Pixmap p;
112 if (canReceiveFocus(WMGetFromArray(panel->windows, idecks)) < 0)
113 opaq = 50;
115 pos = WMGetViewPosition(WMWidgetView(icon));
116 back = panel->tileTmp;
117 if (panel->bg)
118 RCopyArea(back, panel->bg,
119 BORDER_SPACE + pos.x - panel->firstVisible * ICON_TILE_SIZE,
120 BORDER_SPACE + pos.y, back->width, back->height, 0, 0);
121 else {
122 RColor color;
123 WMScreen *wscr = WMWidgetScreen(icon);
124 color.red = 255;
125 color.red = WMRedComponentOfColor(WMGrayColor(wscr)) >> 8;
126 color.green = WMGreenComponentOfColor(WMGrayColor(wscr)) >> 8;
127 color.blue = WMBlueComponentOfColor(WMGrayColor(wscr)) >> 8;
128 RFillImage(back, &color);
130 if (selected) {
131 tile = panel->tile;
132 RCombineArea(back, tile, 0, 0, tile->width, tile->height,
133 (back->width - tile->width) / 2, (back->height - tile->height) / 2);
135 RCombineAreaWithOpaqueness(back, image, 0, 0, image->width, image->height,
136 (back->width - image->width) / 2, (back->height - image->height) / 2,
137 opaq);
139 RConvertImage(panel->scr->rcontext, back, &p);
140 XSetWindowBackgroundPixmap(dpy, WMWidgetXID(icon), p);
141 XClearWindow(dpy, WMWidgetXID(icon));
142 XFreePixmap(dpy, p);
145 if (!panel->bg && !panel->tile) {
146 if (selected)
147 WMSetFrameRelief(icon, WRSimple);
151 static RImage *scaleDownIfNeeded(RImage * image)
153 if (image && ((image->width - ICON_SIZE) > 2 || (image->height - ICON_SIZE) > 2)) {
154 RImage *nimage;
155 nimage = RScaleImage(image, ICON_SIZE, (image->height * ICON_SIZE / image->width));
156 RReleaseImage(image);
157 image = nimage;
159 return image;
162 static void addIconForWindow(WSwitchPanel * panel, WMWidget * parent, WWindow * wwin, int x, int y)
164 WMFrame *icon = WMCreateFrame(parent);
165 RImage *image = NULL;
167 WMSetFrameRelief(icon, WRFlat);
168 WMResizeWidget(icon, ICON_TILE_SIZE, ICON_TILE_SIZE);
169 WMMoveWidget(icon, x, y);
171 if (!WFLAGP(wwin, always_user_icon) && wwin->net_icon_image)
172 image = RRetainImage(wwin->net_icon_image);
174 // Make this use a caching thing. When there are many windows,
175 // it's very likely that most of them are instances of the same thing,
176 // so caching them should get performance acceptable in these cases.
177 if (!image)
178 image = wDefaultGetImage(panel->scr, wwin->wm_instance, wwin->wm_class);
180 if (!image && !panel->defIcon) {
181 char *file = wDefaultGetIconFile(panel->scr, NULL, NULL, False);
182 if (file) {
183 char *path = FindImage(wPreferences.icon_path, file);
184 if (path) {
185 image = RLoadImage(panel->scr->rcontext, path, 0);
186 wfree(path);
189 if (image)
190 panel->defIcon = scaleDownIfNeeded(image);
191 image = NULL;
193 if (!image && panel->defIcon)
194 image = RRetainImage(panel->defIcon);
196 image = scaleDownIfNeeded(image);
198 WMAddToArray(panel->images, image);
199 WMAddToArray(panel->icons, icon);
202 static void scrollIcons(WSwitchPanel * panel, int delta)
204 int nfirst = panel->firstVisible + delta;
205 int i;
206 int count = WMGetArrayItemCount(panel->windows);
207 // int nx, ox;
208 // struct timeval tv1, tv2;
210 if (count <= panel->visibleCount)
211 return;
213 if (nfirst < 0)
214 nfirst = 0;
215 else if (nfirst >= count - panel->visibleCount)
216 nfirst = count - panel->visibleCount;
218 if (nfirst == panel->firstVisible)
219 return;
221 ox = -panel->firstVisible * ICON_TILE_SIZE;
222 nx = -nfirst * ICON_TILE_SIZE;
223 for (i= 0; i < SCROLL_STEPS; i++) {
224 unsigned int diff;
225 gettimeofday(&tv1, NULL);
226 WMMoveWidget(panel->iconBox, (nx*i + ox*(SCROLL_STEPS-i))/(SCROLL_STEPS-1), 0);
227 XSync(dpy, False);
228 gettimeofday(&tv2, NULL);
229 diff = (tv2.tv_sec-tv1.tv_sec)*10000+(tv2.tv_usec-tv1.tv_usec)/100;
230 if (diff < 200)
231 wusleep(300-diff);
234 WMMoveWidget(panel->iconBox, -nfirst * ICON_TILE_SIZE, 0);
236 panel->firstVisible = nfirst;
238 for (i = panel->firstVisible; i < panel->firstVisible + panel->visibleCount; i++) {
239 changeImage(panel, i, i == panel->current);
244 * 0 1 2
245 * 3 4 5
246 * 6 7 8
248 static RImage *assemblePuzzleImage(RImage ** images, int width, int height)
250 RImage *img = RCreateImage(width, height, 1);
251 RImage *tmp;
252 int tw, th;
253 RColor color;
254 if (!img)
255 return NULL;
257 color.red = 0;
258 color.green = 0;
259 color.blue = 0;
260 color.alpha = 255;
262 RFillImage(img, &color);
264 tw = width - images[0]->width - images[2]->width;
265 th = height - images[0]->height - images[6]->height;
267 if (tw <= 0 || th <= 0) {
268 //XXX
269 return NULL;
272 /* top */
273 if (tw > 0) {
274 tmp = RSmoothScaleImage(images[1], tw, images[1]->height);
275 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[0]->width, 0);
276 RReleaseImage(tmp);
278 /* bottom */
279 if (tw > 0) {
280 tmp = RSmoothScaleImage(images[7], tw, images[7]->height);
281 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[6]->width, height - images[6]->height);
282 RReleaseImage(tmp);
284 /* left */
285 if (th > 0) {
286 tmp = RSmoothScaleImage(images[3], images[3]->width, th);
287 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, 0, images[0]->height);
288 RReleaseImage(tmp);
290 /* right */
291 if (th > 0) {
292 tmp = RSmoothScaleImage(images[5], images[5]->width, th);
293 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, width - images[5]->width, images[2]->height);
294 RReleaseImage(tmp);
296 /* center */
297 if (tw > 0 && th > 0) {
298 tmp = RSmoothScaleImage(images[4], tw, th);
299 RCopyArea(img, tmp, 0, 0, tmp->width, tmp->height, images[0]->width, images[0]->height);
300 RReleaseImage(tmp);
303 /* corners */
304 RCopyArea(img, images[0], 0, 0, images[0]->width, images[0]->height, 0, 0);
306 RCopyArea(img, images[2], 0, 0, images[2]->width, images[2]->height, width - images[2]->width, 0);
308 RCopyArea(img, images[6], 0, 0, images[6]->width, images[6]->height, 0, height - images[6]->height);
310 RCopyArea(img, images[8], 0, 0, images[8]->width, images[8]->height,
311 width - images[8]->width, height - images[8]->height);
313 return img;
316 static RImage *createBackImage(WScreen * scr, int width, int height)
318 return assemblePuzzleImage(wPreferences.swbackImage, width, height);
321 static RImage *getTile(WSwitchPanel * panel)
323 RImage *stile;
325 if (!wPreferences.swtileImage)
326 return NULL;
328 stile = RScaleImage(wPreferences.swtileImage, ICON_TILE_SIZE, ICON_TILE_SIZE);
329 if (!stile)
330 return wPreferences.swtileImage;
332 return stile;
335 static void drawTitle(WSwitchPanel * panel, int idecks, char *title)
337 char *ntitle;
338 int width = WMWidgetWidth(panel->win);
339 int x;
341 if (title)
342 ntitle = ShrinkString(panel->font, title, width - 2 * BORDER_SPACE);
343 else
344 ntitle = NULL;
346 if (panel->bg) {
347 if (ntitle) {
348 if (strcmp(ntitle, title) != 0)
349 x = BORDER_SPACE;
350 else {
351 int w = WMWidthOfString(panel->font, ntitle, strlen(ntitle));
353 x = BORDER_SPACE + (idecks - panel->firstVisible) * ICON_TILE_SIZE +
354 ICON_TILE_SIZE / 2 - w / 2;
355 if (x < BORDER_SPACE)
356 x = BORDER_SPACE;
357 else if (x + w > width - BORDER_SPACE)
358 x = width - BORDER_SPACE - w;
361 XClearWindow(dpy, WMWidgetXID(panel->win));
362 if (ntitle)
363 WMDrawString(panel->scr->wmscreen,
364 WMWidgetXID(panel->win),
365 panel->white, panel->font,
367 WMWidgetHeight(panel->win) - BORDER_SPACE - LABEL_HEIGHT +
368 WMFontHeight(panel->font) / 2, ntitle, strlen(ntitle));
369 } else {
370 if (ntitle)
371 WMSetLabelText(panel->label, ntitle);
373 if (ntitle)
374 free(ntitle);
377 static WMArray *makeWindowListArray(WScreen * scr, WWindow * curwin, int workspace, int include_unmapped)
379 WMArray *windows = WMCreateArray(10);
380 int fl;
381 WWindow *wwin;
383 for (fl = 0; fl < 2; fl++) {
384 for (wwin = curwin; wwin; wwin = wwin->prev) {
385 if (((!fl && canReceiveFocus(wwin) > 0) || (fl && canReceiveFocus(wwin) < 0)) &&
386 (!WFLAGP(wwin, skip_window_list) || wwin->flags.internal_window) &&
387 (wwin->flags.mapped || include_unmapped)) {
388 WMAddToArray(windows, wwin);
391 wwin = curwin;
392 /* start over from the beginning of the list */
393 while (wwin->next)
394 wwin = wwin->next;
396 for (wwin = curwin; wwin && wwin != curwin; wwin = wwin->prev) {
397 if (((!fl && canReceiveFocus(wwin) > 0) || (fl && canReceiveFocus(wwin) < 0)) &&
398 (!WFLAGP(wwin, skip_window_list) || wwin->flags.internal_window) &&
399 (wwin->flags.mapped || include_unmapped)) {
400 WMAddToArray(windows, wwin);
405 return windows;
408 WSwitchPanel *wInitSwitchPanel(WScreen * scr, WWindow * curwin, int workspace)
410 WWindow *wwin;
411 WSwitchPanel *panel = wmalloc(sizeof(WSwitchPanel));
412 WMFrame *viewport;
413 int i;
414 int width, height;
415 int iconsThatFitCount;
416 int count;
417 WMRect rect;
418 rect = wGetRectForHead(scr, wGetHeadForPointerLocation(scr));
420 memset(panel, 0, sizeof(WSwitchPanel));
422 panel->scr = scr;
424 panel->windows = makeWindowListArray(scr, curwin, workspace, wPreferences.swtileImage != 0);
425 count = WMGetArrayItemCount(panel->windows);
427 if (count == 0) {
428 WMFreeArray(panel->windows);
429 wfree(panel);
430 return NULL;
433 width = ICON_TILE_SIZE * count;
434 iconsThatFitCount = count;
436 if (width > rect.size.width) {
437 iconsThatFitCount = (rect.size.width - SCREEN_BORDER_SPACING) / ICON_TILE_SIZE;
438 width = iconsThatFitCount * ICON_TILE_SIZE;
441 panel->visibleCount = iconsThatFitCount;
443 if (!wPreferences.swtileImage)
444 return panel;
446 height = LABEL_HEIGHT + ICON_TILE_SIZE;
448 panel->tileTmp = RCreateImage(ICON_TILE_SIZE, ICON_TILE_SIZE, 1);
449 panel->tile = getTile(panel);
450 if (panel->tile && wPreferences.swbackImage[8]) {
451 panel->bg = createBackImage(scr, width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
453 if (!panel->tileTmp || !panel->tile) {
454 if (panel->bg)
455 RReleaseImage(panel->bg);
456 panel->bg = NULL;
457 if (panel->tile)
458 RReleaseImage(panel->tile);
459 panel->tile = NULL;
460 if (panel->tileTmp)
461 RReleaseImage(panel->tileTmp);
462 panel->tileTmp = NULL;
465 panel->white = WMWhiteColor(scr->wmscreen);
466 panel->font = WMBoldSystemFontOfSize(scr->wmscreen, 12);
467 panel->icons = WMCreateArray(count);
468 panel->images = WMCreateArray(count);
470 panel->win = WMCreateWindow(scr->wmscreen, "");
472 if (!panel->bg) {
473 WMFrame *frame = WMCreateFrame(panel->win);
474 WMColor *darkGray = WMDarkGrayColor(scr->wmscreen);
475 WMSetFrameRelief(frame, WRSimple);
476 WMSetViewExpandsToParent(WMWidgetView(frame), 0, 0, 0, 0);
478 panel->label = WMCreateLabel(panel->win);
479 WMResizeWidget(panel->label, width, LABEL_HEIGHT);
480 WMMoveWidget(panel->label, BORDER_SPACE, BORDER_SPACE + ICON_TILE_SIZE + 5);
481 WMSetLabelRelief(panel->label, WRSimple);
482 WMSetWidgetBackgroundColor(panel->label, darkGray);
483 WMSetLabelFont(panel->label, panel->font);
484 WMSetLabelTextColor(panel->label, panel->white);
486 WMReleaseColor(darkGray);
487 height += 5;
490 WMResizeWidget(panel->win, width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
492 viewport = WMCreateFrame(panel->win);
493 WMResizeWidget(viewport, width, ICON_TILE_SIZE);
494 WMMoveWidget(viewport, BORDER_SPACE, BORDER_SPACE);
495 WMSetFrameRelief(viewport, WRFlat);
497 panel->iconBox = WMCreateFrame(viewport);
498 WMMoveWidget(panel->iconBox, 0, 0);
499 WMResizeWidget(panel->iconBox, ICON_TILE_SIZE * count, ICON_TILE_SIZE);
500 WMSetFrameRelief(panel->iconBox, WRFlat);
502 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
503 addIconForWindow(panel, panel->iconBox, wwin, i * ICON_TILE_SIZE, 0);
506 WMMapSubwidgets(panel->win);
507 WMRealizeWidget(panel->win);
509 WM_ITERATE_ARRAY(panel->windows, wwin, i) {
510 changeImage(panel, i, 0);
513 if (panel->bg) {
514 Pixmap pixmap, mask;
516 RConvertImageMask(scr->rcontext, panel->bg, &pixmap, &mask, 250);
518 XSetWindowBackgroundPixmap(dpy, WMWidgetXID(panel->win), pixmap);
520 #ifdef SHAPE
521 if (mask && wShapeSupported)
522 XShapeCombineMask(dpy, WMWidgetXID(panel->win), ShapeBounding, 0, 0, mask, ShapeSet);
523 #endif
525 if (pixmap)
526 XFreePixmap(dpy, pixmap);
527 if (mask)
528 XFreePixmap(dpy, mask);
532 WMPoint center;
533 center = wGetPointToCenterRectInHead(scr, wGetHeadForPointerLocation(scr),
534 width + 2 * BORDER_SPACE, height + 2 * BORDER_SPACE);
535 WMMoveWidget(panel->win, center.x, center.y);
538 panel->current = WMGetFirstInArray(panel->windows, curwin);
539 if (panel->current >= 0)
540 changeImage(panel, panel->current, 1);
542 WMMapWidget(panel->win);
544 return panel;
547 void wSwitchPanelDestroy(WSwitchPanel * panel)
549 int i;
550 RImage *image;
552 if (panel->win) {
553 Window info_win = panel->scr->info_window;
554 XEvent ev;
555 ev.xclient.type = ClientMessage;
556 ev.xclient.message_type = _XA_WM_IGNORE_FOCUS_EVENTS;
557 ev.xclient.format = 32;
558 ev.xclient.data.l[0] = True;
560 XSendEvent(dpy, info_win, True, EnterWindowMask, &ev);
561 WMUnmapWidget(panel->win);
563 ev.xclient.data.l[0] = False;
564 XSendEvent(dpy, info_win, True, EnterWindowMask, &ev);
567 if (panel->images) {
568 WM_ITERATE_ARRAY(panel->images, image, i) {
569 if (image)
570 RReleaseImage(image);
572 WMFreeArray(panel->images);
574 if (panel->win)
575 WMDestroyWidget(panel->win);
576 if (panel->icons)
577 WMFreeArray(panel->icons);
578 WMFreeArray(panel->windows);
579 if (panel->defIcon)
580 RReleaseImage(panel->defIcon);
581 if (panel->tile)
582 RReleaseImage(panel->tile);
583 if (panel->tileTmp)
584 RReleaseImage(panel->tileTmp);
585 if (panel->bg)
586 RReleaseImage(panel->bg);
587 if (panel->font)
588 WMReleaseFont(panel->font);
589 if (panel->white)
590 WMReleaseColor(panel->white);
591 wfree(panel);
594 WWindow *wSwitchPanelSelectNext(WSwitchPanel * panel, int back)
596 WWindow *wwin;
597 int count = WMGetArrayItemCount(panel->windows);
599 if (count == 0)
600 return NULL;
602 if (panel->win)
603 changeImage(panel, panel->current, 0);
605 if (back)
606 panel->current--;
607 else
608 panel->current++;
610 wwin = WMGetFromArray(panel->windows, (count + panel->current) % count);
612 if (back) {
613 if (panel->current < 0)
614 scrollIcons(panel, count);
615 else if (panel->current < panel->firstVisible)
616 scrollIcons(panel, -1);
617 } else {
618 if (panel->current >= count)
619 scrollIcons(panel, -count);
620 else if (panel->current - panel->firstVisible >= panel->visibleCount)
621 scrollIcons(panel, 1);
624 panel->current = (count + panel->current) % count;
626 if (panel->win) {
627 drawTitle(panel, panel->current, wwin->frame->title);
629 changeImage(panel, panel->current, 1);
632 return wwin;
635 WWindow *wSwitchPanelSelectFirst(WSwitchPanel * panel, int back)
637 WWindow *wwin;
638 int count = WMGetArrayItemCount(panel->windows);
640 if (count == 0)
641 return NULL;
643 if (panel->win)
644 changeImage(panel, panel->current, 0);
646 if (back) {
647 panel->current = count - 1;
648 scrollIcons(panel, count);
649 } else {
650 panel->current = 0;
651 scrollIcons(panel, -count);
654 wwin = WMGetFromArray(panel->windows, panel->current);
656 if (panel->win) {
657 drawTitle(panel, panel->current, wwin->frame->title);
659 changeImage(panel, panel->current, 1);
661 return wwin;
664 WWindow *wSwitchPanelHandleEvent(WSwitchPanel * panel, XEvent * event)
666 WMFrame *icon;
667 int i;
668 int focus = -1;
670 if (!panel->win)
671 return NULL;
673 /* if (event->type == LeaveNotify) {
674 if (event->xcrossing.window == WMWidgetXID(panel->win))
675 focus= 0;
676 } else */ if (event->type == MotionNotify) {
678 WM_ITERATE_ARRAY(panel->icons, icon, i) {
679 if (WMWidgetXID(icon) == event->xmotion.window) {
680 focus = i;
681 break;
685 if (focus >= 0 && panel->current != focus) {
686 WWindow *wwin;
688 changeImage(panel, panel->current, 0);
689 changeImage(panel, focus, 1);
690 panel->current = focus;
692 wwin = WMGetFromArray(panel->windows, focus);
694 drawTitle(panel, panel->current, wwin->frame->title);
696 return wwin;
699 return NULL;
702 Window wSwitchPanelGetWindow(WSwitchPanel * swpanel)
704 if (!swpanel->win)
705 return None;
706 return WMWidgetXID(swpanel->win);