Add include of cairo.h when using librsvg
[dana/openbox.git] / openbox / focus_cycle_popup.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    focus_cycle_popup.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "focus_cycle_popup.h"
21 #include "focus_cycle.h"
22 #include "popup.h"
23 #include "client.h"
24 #include "screen.h"
25 #include "focus.h"
26 #include "openbox.h"
27 #include "config.h"
28 #include "window.h"
29 #include "event.h"
30 #include "obrender/render.h"
31
32 #include <X11/Xlib.h>
33 #include <glib.h>
34
35 /* Size of the icons, which can appear inside or outside of a hilite box */
36 #define ICON_SIZE (gint)config_theme_window_list_icon_size
37 /* Size of the hilite box around a window's icon */
38 #define HILITE_SIZE (ICON_SIZE + 2*HILITE_OFFSET)
39 /* Width of the outer ring around the hilite box */
40 #define HILITE_WIDTH 2
41 /* Space between the outer ring around the hilite box and the icon inside it */
42 #define HILITE_MARGIN 1
43 /* Total distance from the edge of the hilite box to the icon inside it */
44 #define HILITE_OFFSET (HILITE_WIDTH + HILITE_MARGIN)
45 /* Margin area around the outside of the dialog */
46 #define OUTSIDE_BORDER 3
47 /* Margin area around the text */
48 #define TEXT_BORDER 2
49 /* Scroll the list-mode list when the cursor gets within this many rows of the
50    top or bottom */
51 #define SCROLL_MARGIN 4
52
53 typedef struct _ObFocusCyclePopup       ObFocusCyclePopup;
54 typedef struct _ObFocusCyclePopupTarget ObFocusCyclePopupTarget;
55
56 struct _ObFocusCyclePopupTarget
57 {
58     ObClient *client;
59     RrImage *icon;
60     gchar *text;
61     Window iconwin;
62     /* This is used when the popup is in list mode */
63     Window textwin;
64 };
65
66 struct _ObFocusCyclePopup
67 {
68     ObWindow obwin;
69     Window bg;
70
71     /* This is used when the popup is in icon mode */
72     Window icon_mode_text;
73
74     Window list_mode_up;
75     Window list_mode_down;
76
77     GList *targets;
78     gint n_targets;
79
80     const ObFocusCyclePopupTarget *last_target;
81
82     gint maxtextw;
83
84     /* How are the list is scrolled, in scroll mode */
85     gint scroll;
86
87     RrAppearance *a_bg;
88     RrAppearance *a_text;
89     RrAppearance *a_hilite_text;
90     RrAppearance *a_icon;
91     RrAppearance *a_arrow;
92
93     gboolean mapped;
94     ObFocusCyclePopupMode mode;
95 };
96
97 /*! This popup shows all possible windows */
98 static ObFocusCyclePopup popup;
99 /*! This popup shows a single window */
100 static ObIconPopup *single_popup;
101
102 static gchar   *popup_get_name (ObClient *c);
103 static gboolean popup_setup    (ObFocusCyclePopup *p,
104                                 gboolean create_targets,
105                                 gboolean refresh_targets,
106                                 gboolean linear);
107 static void     popup_render   (ObFocusCyclePopup *p,
108                                 const ObClient *c);
109
110 static Window create_window(Window parent, guint bwidth, gulong mask,
111                             XSetWindowAttributes *attr)
112 {
113     return XCreateWindow(obt_display, parent, 0, 0, 1, 1, bwidth,
114                          RrDepth(ob_rr_inst), InputOutput,
115                          RrVisual(ob_rr_inst), mask, attr);
116 }
117
118 void focus_cycle_popup_startup(gboolean reconfig)
119 {
120     XSetWindowAttributes attrib;
121     RrPixel32 *p;
122
123     single_popup = icon_popup_new();
124
125     popup.obwin.type = OB_WINDOW_CLASS_INTERNAL;
126     popup.a_bg = RrAppearanceCopy(ob_rr_theme->osd_bg);
127     popup.a_hilite_text = RrAppearanceCopy(ob_rr_theme->osd_hilite_label);
128     popup.a_text = RrAppearanceCopy(ob_rr_theme->osd_unhilite_label);
129     popup.a_icon = RrAppearanceCopy(ob_rr_theme->a_clear);
130     popup.a_arrow = RrAppearanceCopy(ob_rr_theme->a_clear_tex);
131
132     popup.a_hilite_text->surface.parent = popup.a_bg;
133     popup.a_text->surface.parent = popup.a_bg;
134     popup.a_icon->surface.parent = popup.a_bg;
135
136     popup.a_text->texture[0].data.text.justify = RR_JUSTIFY_LEFT;
137     popup.a_hilite_text->texture[0].data.text.justify = RR_JUSTIFY_LEFT;
138
139     /* 2 textures. texture[0] is the icon.  texture[1] is the hilight, and
140        may or may not be used */
141     RrAppearanceAddTextures(popup.a_icon, 2);
142
143     RrAppearanceClearTextures(popup.a_icon);
144     popup.a_icon->texture[0].type = RR_TEXTURE_IMAGE;
145
146     RrAppearanceClearTextures(popup.a_arrow);
147     popup.a_arrow->texture[0].type = RR_TEXTURE_MASK;
148     popup.a_arrow->texture[0].data.mask.color =
149         ob_rr_theme->osd_text_active_color;
150
151     attrib.override_redirect = True;
152     attrib.border_pixel=RrColorPixel(ob_rr_theme->osd_border_color);
153     popup.bg = create_window(obt_root(ob_screen), ob_rr_theme->obwidth,
154                              CWOverrideRedirect | CWBorderPixel, &attrib);
155
156     /* create the text window used for the icon-mode popup */
157     popup.icon_mode_text = create_window(popup.bg, 0, 0, NULL);
158
159     /* create the windows for the up and down arrows */
160     popup.list_mode_up = create_window(popup.bg, 0, 0, NULL);
161     popup.list_mode_down = create_window(popup.bg, 0, 0, NULL);
162
163     popup.targets = NULL;
164     popup.n_targets = 0;
165     popup.last_target = NULL;
166
167     /* set up the hilite texture for the icon */
168     popup.a_icon->texture[1].data.rgba.width = HILITE_SIZE;
169     popup.a_icon->texture[1].data.rgba.height = HILITE_SIZE;
170     popup.a_icon->texture[1].data.rgba.alpha = 0xff;
171     p = g_new(RrPixel32, HILITE_SIZE * HILITE_SIZE);
172     popup.a_icon->texture[1].data.rgba.data = p;
173
174     /* create the hilite under the target icon */
175     {
176         RrPixel32 color;
177         RrColor *tc;
178         gint x, y, o;
179
180         tc = ob_rr_theme->osd_text_active_color;
181         color = ((tc->r & 0xff) << RrDefaultRedOffset) +
182             ((tc->g & 0xff) << RrDefaultGreenOffset) +
183             ((tc->b & 0xff) << RrDefaultBlueOffset);
184
185         o = 0;
186         for (x = 0; x < HILITE_SIZE; x++)
187             for (y = 0; y < HILITE_SIZE; y++) {
188                 guchar a;
189
190                 if (x < HILITE_WIDTH ||
191                     x >= HILITE_SIZE - HILITE_WIDTH ||
192                     y < HILITE_WIDTH ||
193                     y >= HILITE_SIZE - HILITE_WIDTH)
194                 {
195                     /* the border of the target */
196                     a = 0x88;
197                 } else {
198                     /* the background of the target */
199                     a = 0x22;
200                 }
201
202                 p[o++] = color + (a << RrDefaultAlphaOffset);
203             }
204     }
205
206     stacking_add(INTERNAL_AS_WINDOW(&popup));
207     window_add(&popup.bg, INTERNAL_AS_WINDOW(&popup));
208 }
209
210 void focus_cycle_popup_shutdown(gboolean reconfig)
211 {
212     icon_popup_free(single_popup);
213
214     window_remove(popup.bg);
215     stacking_remove(INTERNAL_AS_WINDOW(&popup));
216
217     while(popup.targets) {
218         ObFocusCyclePopupTarget *t = popup.targets->data;
219
220         RrImageUnref(t->icon);
221         g_free(t->text);
222         XDestroyWindow(obt_display, t->iconwin);
223         XDestroyWindow(obt_display, t->textwin);
224         g_slice_free(ObFocusCyclePopupTarget, t);
225
226         popup.targets = g_list_delete_link(popup.targets, popup.targets);
227     }
228
229     g_free(popup.a_icon->texture[1].data.rgba.data);
230     popup.a_icon->texture[1].data.rgba.data = NULL;
231
232     XDestroyWindow(obt_display, popup.list_mode_up);
233     XDestroyWindow(obt_display, popup.list_mode_down);
234     XDestroyWindow(obt_display, popup.icon_mode_text);
235     XDestroyWindow(obt_display, popup.bg);
236
237     RrAppearanceFree(popup.a_arrow);
238     RrAppearanceFree(popup.a_icon);
239     RrAppearanceFree(popup.a_hilite_text);
240     RrAppearanceFree(popup.a_text);
241     RrAppearanceFree(popup.a_bg);
242 }
243
244 static void popup_target_free(ObFocusCyclePopupTarget *t)
245 {
246     RrImageUnref(t->icon);
247     g_free(t->text);
248     XDestroyWindow(obt_display, t->iconwin);
249     XDestroyWindow(obt_display, t->textwin);
250     g_slice_free(ObFocusCyclePopupTarget, t);
251 }
252
253 static gboolean popup_setup(ObFocusCyclePopup *p, gboolean create_targets,
254                             gboolean refresh_targets, gboolean linear)
255 {
256     gint maxwidth, n;
257     GList *it;
258     GList *rtargets; /* old targets for refresh */
259     GList *rtlast;
260     gboolean change;
261
262     if (refresh_targets) {
263         rtargets = p->targets;
264         rtlast = g_list_last(rtargets);
265         p->targets = NULL;
266         p->n_targets = 0;
267         change = FALSE;
268     }
269     else {
270         rtargets = rtlast = NULL;
271         change = TRUE;
272     }
273
274     g_assert(p->targets == NULL);
275     g_assert(p->n_targets == 0);
276
277     /* make its width to be the width of all the possible titles */
278
279     /* build a list of all the valid focus targets and measure their strings,
280        and count them */
281     maxwidth = 0;
282     n = 0;
283     for (it = g_list_last(linear ? client_list : focus_order);
284          it;
285          it = g_list_previous(it))
286     {
287         ObClient *ft = it->data;
288
289         if (focus_cycle_valid(ft)) {
290             GList *rit;
291
292             /* reuse the target if possible during refresh */
293             for (rit = rtlast; rit; rit = g_list_previous(rit)) {
294                 ObFocusCyclePopupTarget *t = rit->data;
295                 if (t->client == ft) {
296                     if (rit == rtlast)
297                         rtlast = g_list_previous(rit);
298                     rtargets = g_list_remove_link(rtargets, rit);
299
300                     p->targets = g_list_concat(rit, p->targets);
301                     ++n;
302
303                     if (rit != rtlast)
304                         change = TRUE; /* order changed */
305                     break;
306                 }
307             }
308
309             if (!rit) {
310                 gchar *text = popup_get_name(ft);
311
312                 /* measure */
313                 p->a_text->texture[0].data.text.string = text;
314                 maxwidth = MAX(maxwidth, RrMinWidth(p->a_text));
315
316                 if (!create_targets) {
317                     g_free(text);
318                 } else {
319                     ObFocusCyclePopupTarget *t =
320                         g_slice_new(ObFocusCyclePopupTarget);
321
322                     t->client = ft;
323                     t->text = text;
324                     t->icon = client_icon(t->client);
325                     RrImageRef(t->icon); /* own the icon so it won't go away */
326                     t->iconwin = create_window(p->bg, 0, 0, NULL);
327                     t->textwin = create_window(p->bg, 0, 0, NULL);
328
329                     p->targets = g_list_prepend(p->targets, t);
330                     ++n;
331
332                     change = TRUE; /* added a window */
333                 }
334             }
335         }
336     }
337
338     if (rtargets) {
339         change = TRUE; /* removed a window */
340
341         while (rtargets) {
342             popup_target_free(rtargets->data);
343             rtargets = g_list_delete_link(rtargets, rtargets);
344         }
345     }
346
347     p->n_targets = n;
348     if (refresh_targets)
349         /* don't shrink when refreshing */
350         p->maxtextw = MAX(p->maxtextw, maxwidth);
351     else
352         p->maxtextw = maxwidth;
353
354     return change;
355 }
356
357 static void popup_cleanup(void)
358 {
359     while(popup.targets) {
360         popup_target_free(popup.targets->data);
361         popup.targets = g_list_delete_link(popup.targets, popup.targets);
362     }
363     popup.n_targets = 0;
364     popup.last_target = NULL;
365 }
366
367 static gchar *popup_get_name(ObClient *c)
368 {
369     ObClient *p;
370     gchar *title;
371     const gchar *desk = NULL;
372     gchar *ret;
373
374     /* find our highest direct parent */
375     p = client_search_top_direct_parent(c);
376
377     if (c->desktop != DESKTOP_ALL && c->desktop != screen_desktop)
378         desk = screen_desktop_names[c->desktop];
379
380     title = c->iconic ? c->icon_title : c->title;
381
382     /* use the transient's parent's title/icon if we don't have one */
383     if (p != c && title[0] == '\0')
384         title = p->iconic ? p->icon_title : p->title;
385
386     if (desk)
387         ret = g_strdup_printf("%s [%s]", title, desk);
388     else
389         ret = g_strdup(title);
390
391     return ret;
392 }
393
394 static void popup_render(ObFocusCyclePopup *p, const ObClient *c)
395 {
396     gint ml, mt, mr, mb;
397     gint l, t, r, b;
398     gint x, y, w, h;
399     const Rect *screen_area = NULL;
400     gint i;
401     GList *it;
402     const ObFocusCyclePopupTarget *newtarget;
403     ObFocusCyclePopupMode mode = p->mode;
404     gint icons_per_row;
405     gint icon_rows;
406     gint textw, texth;
407     gint selected_pos;
408     gint last_scroll;
409
410     /* vars for icon mode */
411     gint icon_mode_textx;
412     gint icon_mode_texty;
413     gint icons_center_x;
414
415     /* vars for list mode */
416     gint list_mode_icon_column_w = HILITE_SIZE + OUTSIDE_BORDER;
417     gint up_arrow_x, down_arrow_x;
418     gint up_arrow_y, down_arrow_y;
419     gboolean showing_arrows = FALSE;
420
421     g_assert(mode == OB_FOCUS_CYCLE_POPUP_MODE_ICONS ||
422              mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST);
423
424     screen_area = screen_physical_area_primary(FALSE);
425
426     /* get the outside margins */
427     RrMargins(p->a_bg, &ml, &mt, &mr, &mb);
428
429     /* get our outside borders */
430     l = ml + OUTSIDE_BORDER;
431     r = mr + OUTSIDE_BORDER;
432     t = mt + OUTSIDE_BORDER;
433     b = mb + OUTSIDE_BORDER;
434
435     /* get the width from the text and keep it within limits */
436     w = l + r + p->maxtextw;
437     if (mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST)
438         /* when in list mode, there are icons down the side */
439         w += list_mode_icon_column_w;
440     w = MIN(w, MAX(screen_area->width/3, POPUP_WIDTH)); /* max width */
441     w = MAX(w, POPUP_WIDTH); /* min width */
442
443     /* get the text height */
444     texth = RrMinHeight(p->a_hilite_text);
445     if (mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST)
446         texth = MAX(MAX(texth, RrMinHeight(p->a_text)), HILITE_SIZE);
447     else
448         texth += TEXT_BORDER * 2;
449
450     if (mode == OB_FOCUS_CYCLE_POPUP_MODE_ICONS) {
451         /* how many icons will fit in that row? make the width fit that */
452         w -= l + r;
453         icons_per_row = (w + HILITE_SIZE - 1) / HILITE_SIZE;
454         w = icons_per_row * HILITE_SIZE + l + r;
455
456         /* how many rows do we need? */
457         icon_rows = (p->n_targets-1) / icons_per_row + 1;
458     } else {
459         /* in list mode, there is one column of icons.. */
460         icons_per_row = 1;
461         /* maximum is 80% of the screen height */
462         icon_rows = MIN(p->n_targets,
463                         (4*screen_area->height/5) /* 80% of the screen */
464                         /
465                         MAX(HILITE_SIZE, texth)); /* height of each row */
466         /* but make sure there is always one */
467         icon_rows = MAX(icon_rows, 1);
468     }
469
470     /* get the text width */
471     textw = w - l - r;
472     if (mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST)
473         /* leave space on the side for the icons */
474         textw -= list_mode_icon_column_w;
475
476     if (!p->mapped)
477         /* reset the scrolling when the dialog is first shown */
478         p->scroll = 0;
479
480     /* find the height of the dialog */
481     h = t + b + (icon_rows * MAX(HILITE_SIZE, texth));
482     if (mode == OB_FOCUS_CYCLE_POPUP_MODE_ICONS)
483         /* in icon mode the text sits below the icons, so make some space */
484         h += OUTSIDE_BORDER + texth;
485
486     /* find the focused target */
487     newtarget = NULL;
488     for (i = 0, it = p->targets; it; ++i, it = g_list_next(it)) {
489         const ObFocusCyclePopupTarget *target = it->data;
490         if (target->client == c) {
491             /* save the target */
492             newtarget = target;
493             break;
494         }
495     }
496     selected_pos = i;
497     g_assert(newtarget != NULL);
498
499     /* scroll the list if needed */
500     last_scroll = p->scroll;
501     if (mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST) {
502         const gint top = p->scroll + SCROLL_MARGIN;
503         const gint bottom = p->scroll + icon_rows - SCROLL_MARGIN;
504         const gint min_scroll = 0;
505         const gint max_scroll = p->n_targets - icon_rows;
506
507         if (top - selected_pos >= 0) {
508             p->scroll -= top - selected_pos + 1;
509             p->scroll = MAX(p->scroll, min_scroll);
510         } else if (selected_pos - bottom >= 0) {
511             p->scroll += selected_pos - bottom + 1;
512             p->scroll = MIN(p->scroll, max_scroll);
513         }
514     }
515
516     /* show the scroll arrows when appropriate */
517     if (p->scroll && mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST) {
518         XMapWindow(obt_display, p->list_mode_up);
519         showing_arrows = TRUE;
520     } else
521         XUnmapWindow(obt_display, p->list_mode_up);
522
523     if (p->scroll < p->n_targets - icon_rows &&
524         mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST)
525     {
526         XMapWindow(obt_display, p->list_mode_down);
527         showing_arrows = TRUE;
528     } else
529         XUnmapWindow(obt_display, p->list_mode_down);
530
531     /* make space for the arrows */
532     if (showing_arrows)
533         h += ob_rr_theme->up_arrow_mask->height + OUTSIDE_BORDER
534             + ob_rr_theme->down_arrow_mask->height + OUTSIDE_BORDER;
535
536     /* center the icons if there is less than one row */
537     if (mode == OB_FOCUS_CYCLE_POPUP_MODE_ICONS && icon_rows == 1)
538         icons_center_x = (w - p->n_targets * HILITE_SIZE) / 2;
539     else
540         icons_center_x = 0;
541
542     if (mode == OB_FOCUS_CYCLE_POPUP_MODE_ICONS) {
543         /* get the position of the text */
544         icon_mode_textx = l;
545         icon_mode_texty = h - texth - b;
546     }
547
548     /* find the position for the popup (include the outer borders) */
549     x = screen_area->x + (screen_area->width -
550                           (w + ob_rr_theme->obwidth * 2)) / 2;
551     y = screen_area->y + (screen_area->height -
552                           (h + ob_rr_theme->obwidth * 2)) / 2;
553
554     if (!p->mapped) {
555         /* position the background but don't draw it */
556         XMoveResizeWindow(obt_display, p->bg, x, y, w, h);
557
558         if (mode == OB_FOCUS_CYCLE_POPUP_MODE_ICONS) {
559             /* position the text */
560             XMoveResizeWindow(obt_display, p->icon_mode_text,
561                               icon_mode_textx, icon_mode_texty, textw, texth);
562             XMapWindow(obt_display, popup.icon_mode_text);
563         } else {
564             XUnmapWindow(obt_display, popup.icon_mode_text);
565
566             up_arrow_x = (w - ob_rr_theme->up_arrow_mask->width) / 2;
567             up_arrow_y = t;
568
569             down_arrow_x = (w - ob_rr_theme->down_arrow_mask->width) / 2;
570             down_arrow_y = h - b - ob_rr_theme->down_arrow_mask->height;
571
572             /* position the arrows */
573             XMoveResizeWindow(obt_display, p->list_mode_up,
574                               up_arrow_x, up_arrow_y,
575                               ob_rr_theme->up_arrow_mask->width,
576                               ob_rr_theme->up_arrow_mask->height);
577             XMoveResizeWindow(obt_display, p->list_mode_down,
578                               down_arrow_x, down_arrow_y,
579                               ob_rr_theme->down_arrow_mask->width,
580                               ob_rr_theme->down_arrow_mask->height);
581         }
582     }
583
584     /* * * draw everything * * */
585
586     /* draw the background */
587     if (!p->mapped)
588         RrPaint(p->a_bg, p->bg, w, h);
589
590     /* draw the scroll arrows */
591     if (!p->mapped && mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST) {
592         p->a_arrow->texture[0].data.mask.mask =
593             ob_rr_theme->up_arrow_mask;
594         p->a_arrow->surface.parent = p->a_bg;
595         p->a_arrow->surface.parentx = up_arrow_x;
596         p->a_arrow->surface.parenty = up_arrow_y;
597         RrPaint(p->a_arrow, p->list_mode_up, 
598                 ob_rr_theme->up_arrow_mask->width,
599                 ob_rr_theme->up_arrow_mask->height);
600
601         p->a_arrow->texture[0].data.mask.mask =
602             ob_rr_theme->down_arrow_mask;
603         p->a_arrow->surface.parent = p->a_bg;
604         p->a_arrow->surface.parentx = down_arrow_x;
605         p->a_arrow->surface.parenty = down_arrow_y;
606         RrPaint(p->a_arrow, p->list_mode_down, 
607                 ob_rr_theme->down_arrow_mask->width,
608                 ob_rr_theme->down_arrow_mask->height);
609     }
610
611     /* draw the icons and text */
612     for (i = 0, it = p->targets; it; ++i, it = g_list_next(it)) {
613         const ObFocusCyclePopupTarget *target = it->data;
614
615         /* have to redraw the targetted icon and last targetted icon
616          * to update the hilite */
617         if (!p->mapped || newtarget == target || p->last_target == target ||
618             last_scroll != p->scroll)
619         {
620             /* row and column start from 0 */
621             const gint row = i / icons_per_row - p->scroll;
622             const gint col = i % icons_per_row;
623             gint iconx, icony;
624             gint list_mode_textx, list_mode_texty;
625             RrAppearance *text;
626
627             /* find the coordinates for the icon */
628             iconx = icons_center_x + l + (col * HILITE_SIZE);
629             icony = t + (showing_arrows ? ob_rr_theme->up_arrow_mask->height
630                                           + OUTSIDE_BORDER
631                          : 0)
632                 + (row * MAX(texth, HILITE_SIZE))
633                 + MAX(texth - HILITE_SIZE, 0) / 2;
634
635             /* find the dimensions of the text box */
636             list_mode_textx = iconx + HILITE_SIZE + TEXT_BORDER;
637             list_mode_texty = icony;
638
639             /* position the icon */
640             XMoveResizeWindow(obt_display, target->iconwin,
641                               iconx, icony, HILITE_SIZE, HILITE_SIZE);
642
643             /* position the text */
644             if (mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST)
645                 XMoveResizeWindow(obt_display, target->textwin,
646                                   list_mode_textx, list_mode_texty,
647                                   textw, texth);
648
649             /* show/hide the right windows */
650             if (row >= 0 && row < icon_rows) {
651                 XMapWindow(obt_display, target->iconwin);
652                 if (mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST)
653                     XMapWindow(obt_display, target->textwin);
654                 else
655                     XUnmapWindow(obt_display, target->textwin);
656             } else {
657                 XUnmapWindow(obt_display, target->textwin);
658                 if (mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST)
659                     XUnmapWindow(obt_display, target->iconwin);
660                 else
661                     XMapWindow(obt_display, target->iconwin);
662             }
663
664             /* get the icon from the client */
665             p->a_icon->texture[0].data.image.twidth = ICON_SIZE;
666             p->a_icon->texture[0].data.image.theight = ICON_SIZE;
667             p->a_icon->texture[0].data.image.tx = HILITE_OFFSET;
668             p->a_icon->texture[0].data.image.ty = HILITE_OFFSET;
669             p->a_icon->texture[0].data.image.alpha =
670                 target->client->iconic ? OB_ICONIC_ALPHA : 0xff;
671             p->a_icon->texture[0].data.image.image = target->icon;
672
673             /* Draw the hilite? */
674             p->a_icon->texture[1].type = (target == newtarget) ?
675                 RR_TEXTURE_RGBA : RR_TEXTURE_NONE;
676
677             /* draw the icon */
678             p->a_icon->surface.parentx = iconx;
679             p->a_icon->surface.parenty = icony;
680             RrPaint(p->a_icon, target->iconwin, HILITE_SIZE, HILITE_SIZE);
681
682             /* draw the text */
683             if (mode == OB_FOCUS_CYCLE_POPUP_MODE_LIST ||
684                 target == newtarget)
685             {
686                 text = (target == newtarget) ? p->a_hilite_text : p->a_text;
687                 text->texture[0].data.text.string = target->text;
688                 text->surface.parentx =
689                     mode == OB_FOCUS_CYCLE_POPUP_MODE_ICONS ?
690                     icon_mode_textx : list_mode_textx;
691                 text->surface.parenty =
692                     mode == OB_FOCUS_CYCLE_POPUP_MODE_ICONS ?
693                     icon_mode_texty : list_mode_texty;
694                 RrPaint(text,
695                         (mode == OB_FOCUS_CYCLE_POPUP_MODE_ICONS ?
696                          p->icon_mode_text : target->textwin),
697                         textw, texth);
698             }
699         }
700     }
701
702     p->last_target = newtarget;
703
704     XFlush(obt_display);
705 }
706
707 void focus_cycle_popup_show(ObClient *c, ObFocusCyclePopupMode mode,
708                             gboolean linear)
709 {
710     g_assert(c != NULL);
711
712     if (mode == OB_FOCUS_CYCLE_POPUP_MODE_NONE) {
713         focus_cycle_popup_hide();
714         return;
715     }
716
717     /* do this stuff only when the dialog is first showing */
718     if (!popup.mapped) {
719         popup_setup(&popup, TRUE, FALSE, linear);
720         /* this is fixed once the dialog is shown */
721         popup.mode = mode;
722     }
723     g_assert(popup.targets != NULL);
724
725     popup_render(&popup, c);
726
727     if (!popup.mapped) {
728         /* show the dialog */
729         XMapWindow(obt_display, popup.bg);
730         XFlush(obt_display);
731         popup.mapped = TRUE;
732         screen_hide_desktop_popup();
733     }
734 }
735
736 void focus_cycle_popup_hide(void)
737 {
738     gulong ignore_start;
739
740     ignore_start = event_start_ignore_all_enters();
741
742     XUnmapWindow(obt_display, popup.bg);
743     XFlush(obt_display);
744
745     event_end_ignore_all_enters(ignore_start);
746
747     popup.mapped = FALSE;
748
749     popup_cleanup();
750 }
751
752 void focus_cycle_popup_single_show(struct _ObClient *c)
753 {
754     gchar *text;
755
756     g_assert(c != NULL);
757
758     /* do this stuff only when the dialog is first showing */
759     if (!single_popup->popup->mapped) {
760         const Rect *a;
761
762         /* position the popup */
763         a = screen_physical_area_primary(FALSE);
764         icon_popup_position(single_popup, CenterGravity,
765                             a->x + a->width / 2, a->y + a->height / 2);
766         icon_popup_height(single_popup, POPUP_HEIGHT);
767         icon_popup_min_width(single_popup, POPUP_WIDTH);
768         icon_popup_max_width(single_popup, MAX(a->width/3, POPUP_WIDTH));
769         icon_popup_text_width(single_popup, popup.maxtextw);
770     }
771
772     text = popup_get_name(c);
773     icon_popup_show(single_popup, text, client_icon(c));
774     g_free(text);
775     screen_hide_desktop_popup();
776 }
777
778 void focus_cycle_popup_single_hide(void)
779 {
780     icon_popup_hide(single_popup);
781 }
782
783 gboolean focus_cycle_popup_is_showing(ObClient *c)
784 {
785     if (popup.mapped) {
786         GList *it;
787
788         for (it = popup.targets; it; it = g_list_next(it)) {
789             ObFocusCyclePopupTarget *t = it->data;
790             if (t->client == c)
791                 return TRUE;
792         }
793     }
794     return FALSE;
795 }
796
797 static ObClient* popup_revert(ObClient *target)
798 {
799     GList *it, *itt;
800
801     for (it = popup.targets; it; it = g_list_next(it)) {
802         ObFocusCyclePopupTarget *t = it->data;
803         if (t->client == target) {
804             /* move to a previous window if possible */
805             for (itt = it->prev; itt; itt = g_list_previous(itt)) {
806                 ObFocusCyclePopupTarget *t2 = itt->data;
807                 if (focus_cycle_valid(t2->client))
808                     return t2->client;
809             }
810
811             /* otherwise move to a following window if possible */
812             for (itt = it->next; itt; itt = g_list_next(itt)) {
813                 ObFocusCyclePopupTarget *t2 = itt->data;
814                 if (focus_cycle_valid(t2->client))
815                     return t2->client;
816             }
817
818             /* otherwise, we can't go anywhere there is nowhere valid to go */
819             return NULL;
820         }
821     }
822     return NULL;
823 }
824
825 ObClient* focus_cycle_popup_refresh(ObClient *target,
826                                     gboolean redraw,
827                                     gboolean linear)
828 {
829     if (!popup.mapped) return NULL;
830
831     if (!focus_cycle_valid(target))
832         target = popup_revert(target);
833
834     redraw = popup_setup(&popup, TRUE, TRUE, linear) && redraw;
835
836     if (!target && popup.targets)
837         target = ((ObFocusCyclePopupTarget*)popup.targets->data)->client;
838
839     if (target && redraw) {
840         popup.mapped = FALSE;
841         popup_render(&popup, target);
842         XFlush(obt_display);
843         popup.mapped = TRUE;
844     }
845
846     return target;
847 }