]> Dogcows Code - chaz/openbox/blob - openbox/focus_cycle_popup.c
Make the focus cycle indicator follow target fallback in the popup
[chaz/openbox] / 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 "window.h"
28 #include "event.h"
29 #include "render/render.h"
30
31 #include <X11/Xlib.h>
32 #include <glib.h>
33
34 #define ICON_SIZE 40
35 #define ICON_HILITE_WIDTH 2
36 #define ICON_HILITE_MARGIN 1
37 #define OUTSIDE_BORDER 3
38 #define TEXT_BORDER 2
39
40 typedef struct _ObFocusCyclePopup ObFocusCyclePopup;
41 typedef struct _ObFocusCyclePopupTarget ObFocusCyclePopupTarget;
42
43 struct _ObFocusCyclePopupTarget
44 {
45 ObClient *client;
46 RrImage *icon;
47 gchar *text;
48 Window win;
49 };
50
51 struct _ObFocusCyclePopup
52 {
53 ObWindow obwin;
54 Window bg;
55
56 Window text;
57
58 GList *targets;
59 gint n_targets;
60
61 const ObFocusCyclePopupTarget *last_target;
62
63 gint maxtextw;
64
65 RrAppearance *a_bg;
66 RrAppearance *a_text;
67 RrAppearance *a_icon;
68
69 RrPixel32 *hilite_rgba;
70
71 gboolean mapped;
72 };
73
74 /*! This popup shows all possible windows */
75 static ObFocusCyclePopup popup;
76 /*! This popup shows a single window */
77 static ObIconPopup *single_popup;
78
79 static gchar *popup_get_name (ObClient *c);
80 static gboolean popup_setup (ObFocusCyclePopup *p,
81 gboolean create_targets,
82 gboolean refresh_targets);
83 static void popup_render (ObFocusCyclePopup *p,
84 const ObClient *c);
85
86 static Window create_window(Window parent, guint bwidth, gulong mask,
87 XSetWindowAttributes *attr)
88 {
89 return XCreateWindow(ob_display, parent, 0, 0, 1, 1, bwidth,
90 RrDepth(ob_rr_inst), InputOutput,
91 RrVisual(ob_rr_inst), mask, attr);
92 }
93
94 void focus_cycle_popup_startup(gboolean reconfig)
95 {
96 XSetWindowAttributes attrib;
97
98 single_popup = icon_popup_new();
99
100 popup.obwin.type = Window_Internal;
101 popup.a_bg = RrAppearanceCopy(ob_rr_theme->osd_hilite_bg);
102 popup.a_text = RrAppearanceCopy(ob_rr_theme->osd_hilite_label);
103 popup.a_icon = RrAppearanceCopy(ob_rr_theme->a_clear_tex);
104
105 popup.a_text->surface.parent = popup.a_bg;
106 popup.a_icon->surface.parent = popup.a_bg;
107
108 RrAppearanceClearTextures(popup.a_icon);
109 popup.a_icon->texture[0].type = RR_TEXTURE_IMAGE;
110
111 RrAppearanceAddTextures(popup.a_bg, 1);
112 popup.a_bg->texture[0].type = RR_TEXTURE_RGBA;
113
114 attrib.override_redirect = True;
115 attrib.border_pixel=RrColorPixel(ob_rr_theme->osd_border_color);
116 popup.bg = create_window(RootWindow(ob_display, ob_screen),
117 ob_rr_theme->obwidth,
118 CWOverrideRedirect | CWBorderPixel, &attrib);
119
120 popup.text = create_window(popup.bg, 0, 0, NULL);
121
122 popup.targets = NULL;
123 popup.n_targets = 0;
124 popup.last_target = NULL;
125
126 popup.hilite_rgba = NULL;
127
128 XMapWindow(ob_display, popup.text);
129
130 stacking_add(INTERNAL_AS_WINDOW(&popup));
131 g_hash_table_insert(window_map, &popup.bg, &popup);
132 }
133
134 void focus_cycle_popup_shutdown(gboolean reconfig)
135 {
136 icon_popup_free(single_popup);
137
138 g_hash_table_remove(window_map, &popup.bg);
139 stacking_remove(INTERNAL_AS_WINDOW(&popup));
140
141 while(popup.targets) {
142 ObFocusCyclePopupTarget *t = popup.targets->data;
143
144 RrImageUnref(t->icon);
145 g_free(t->text);
146 XDestroyWindow(ob_display, t->win);
147 g_free(t);
148
149 popup.targets = g_list_delete_link(popup.targets, popup.targets);
150 }
151
152 g_free(popup.hilite_rgba);
153 popup.hilite_rgba = NULL;
154
155 XDestroyWindow(ob_display, popup.text);
156 XDestroyWindow(ob_display, popup.bg);
157
158 RrAppearanceFree(popup.a_icon);
159 RrAppearanceFree(popup.a_text);
160 RrAppearanceFree(popup.a_bg);
161 }
162
163 static void popup_target_free(ObFocusCyclePopupTarget *t)
164 {
165 RrImageUnref(t->icon);
166 g_free(t->text);
167 XDestroyWindow(ob_display, t->win);
168 g_free(t);
169 }
170
171 static gboolean popup_setup(ObFocusCyclePopup *p, gboolean create_targets,
172 gboolean refresh_targets)
173 {
174 gint maxwidth, n;
175 GList *it;
176 GList *rtargets; /* old targets for refresh */
177 GList *rtlast;
178 gboolean change;
179
180 if (refresh_targets) {
181 rtargets = p->targets;
182 rtlast = g_list_last(rtargets);
183 p->targets = NULL;
184 p->n_targets = 0;
185 change = FALSE;
186 }
187 else {
188 rtargets = rtlast = NULL;
189 change = TRUE;
190 }
191
192 g_assert(p->targets == NULL);
193 g_assert(p->n_targets == 0);
194
195 /* make its width to be the width of all the possible titles */
196
197 /* build a list of all the valid focus targets and measure their strings,
198 and count them */
199 maxwidth = 0;
200 n = 0;
201 for (it = g_list_last(focus_order); it; it = g_list_previous(it)) {
202 ObClient *ft = it->data;
203
204 if (focus_cycle_valid(ft)) {
205 GList *rit;
206
207 /* reuse the target if possible during refresh */
208 for (rit = rtlast; rit; rit = g_list_previous(rit)) {
209 ObFocusCyclePopupTarget *t = rit->data;
210 if (t->client == ft) {
211 if (rit == rtlast)
212 rtlast = g_list_previous(rit);
213 rtargets = g_list_remove_link(rtargets, rit);
214
215 p->targets = g_list_concat(rit, p->targets);
216 ++n;
217
218 if (rit != rtlast)
219 change = TRUE; /* order changed */
220 break;
221 }
222 }
223
224 if (!rit) {
225 gchar *text = popup_get_name(ft);
226
227 /* measure */
228 p->a_text->texture[0].data.text.string = text;
229 maxwidth = MAX(maxwidth, RrMinWidth(p->a_text));
230
231 if (!create_targets)
232 g_free(text);
233 else {
234 ObFocusCyclePopupTarget *t =
235 g_new(ObFocusCyclePopupTarget, 1);
236
237 t->client = ft;
238 t->text = text;
239 t->icon = client_icon(t->client);
240 RrImageRef(t->icon); /* own the icon so it won't go away */
241 t->win = create_window(p->bg, 0, 0, NULL);
242
243 XMapWindow(ob_display, t->win);
244
245 p->targets = g_list_prepend(p->targets, t);
246 ++n;
247
248 change = TRUE; /* added a window */
249 }
250 }
251 }
252 }
253
254 if (rtargets) {
255 change = TRUE; /* removed a window */
256
257 while (rtargets) {
258 popup_target_free(rtargets->data);
259 rtargets = g_list_delete_link(rtargets, rtargets);
260 }
261 }
262
263 p->n_targets = n;
264 p->maxtextw = maxwidth;
265
266 return change;
267 }
268
269 static void popup_cleanup(void)
270 {
271 while(popup.targets) {
272 popup_target_free(popup.targets->data);
273 popup.targets = g_list_delete_link(popup.targets, popup.targets);
274 }
275 popup.n_targets = 0;
276 popup.last_target = NULL;
277 }
278
279 static gchar *popup_get_name(ObClient *c)
280 {
281 ObClient *p;
282 gchar *title;
283 const gchar *desk = NULL;
284 gchar *ret;
285
286 /* find our highest direct parent */
287 p = client_search_top_direct_parent(c);
288
289 if (c->desktop != DESKTOP_ALL && c->desktop != screen_desktop)
290 desk = screen_desktop_names[c->desktop];
291
292 title = c->iconic ? c->icon_title : c->title;
293
294 /* use the transient's parent's title/icon if we don't have one */
295 if (p != c && title[0] == '\0')
296 title = p->iconic ? p->icon_title : p->title;
297
298 if (desk)
299 ret = g_strdup_printf("%s [%s]", title, desk);
300 else
301 ret = g_strdup(title);
302
303 return ret;
304 }
305
306 static void popup_render(ObFocusCyclePopup *p, const ObClient *c)
307 {
308 gint ml, mt, mr, mb;
309 gint l, t, r, b;
310 gint x, y, w, h;
311 Rect *screen_area = NULL;
312 gint icons_per_row;
313 gint icon_rows;
314 gint textx, texty, textw, texth;
315 gint rgbax, rgbay, rgbaw, rgbah;
316 gint icons_center_x;
317 gint innerw, innerh;
318 gint i;
319 GList *it;
320 const ObFocusCyclePopupTarget *newtarget;
321 gint newtargetx, newtargety;
322
323 screen_area = screen_physical_area_primary(FALSE);
324
325 /* get the outside margins */
326 RrMargins(p->a_bg, &ml, &mt, &mr, &mb);
327
328 /* get our outside borders */
329 l = ml + OUTSIDE_BORDER;
330 r = mr + OUTSIDE_BORDER;
331 t = mt + OUTSIDE_BORDER;
332 b = mb + OUTSIDE_BORDER;
333
334 /* get the icon pictures' sizes */
335 innerw = ICON_SIZE - (ICON_HILITE_WIDTH + ICON_HILITE_MARGIN) * 2;
336 innerh = ICON_SIZE - (ICON_HILITE_WIDTH + ICON_HILITE_MARGIN) * 2;
337
338 /* get the width from the text and keep it within limits */
339 w = l + r + p->maxtextw;
340 w = MIN(w, MAX(screen_area->width/3, POPUP_WIDTH)); /* max width */
341 w = MAX(w, POPUP_WIDTH); /* min width */
342
343 /* how many icons will fit in that row? make the width fit that */
344 w -= l + r;
345 icons_per_row = (w + ICON_SIZE - 1) / ICON_SIZE;
346 w = icons_per_row * ICON_SIZE + l + r;
347
348 /* how many rows do we need? */
349 icon_rows = (p->n_targets-1) / icons_per_row + 1;
350
351 /* get the text dimensions */
352 textw = w - l - r;
353 texth = RrMinHeight(p->a_text) + TEXT_BORDER * 2;
354
355 /* find the height of the dialog */
356 h = t + b + (icon_rows * ICON_SIZE) + (OUTSIDE_BORDER + texth);
357
358 /* get the position of the text */
359 textx = l;
360 texty = h - texth - b;
361
362 /* find the position for the popup (include the outer borders) */
363 x = screen_area->x + (screen_area->width -
364 (w + ob_rr_theme->obwidth * 2)) / 2;
365 y = screen_area->y + (screen_area->height -
366 (h + ob_rr_theme->obwidth * 2)) / 2;
367
368 /* get the dimensions of the target hilite texture */
369 rgbax = ml;
370 rgbay = mt;
371 rgbaw = w - ml - mr;
372 rgbah = h - mt - mb;
373
374 /* center the icons if there is less than one row */
375 if (icon_rows == 1)
376 icons_center_x = (w - p->n_targets * ICON_SIZE) / 2;
377 else
378 icons_center_x = 0;
379
380 if (!p->mapped) {
381 /* position the background but don't draw it*/
382 XMoveResizeWindow(ob_display, p->bg, x, y, w, h);
383
384 /* set up the hilite texture for the background */
385 p->a_bg->texture[0].data.rgba.width = rgbaw;
386 p->a_bg->texture[0].data.rgba.height = rgbah;
387 p->a_bg->texture[0].data.rgba.alpha = 0xff;
388 p->hilite_rgba = g_new(RrPixel32, rgbaw * rgbah);
389 p->a_bg->texture[0].data.rgba.data = p->hilite_rgba;
390
391 /* position the text, but don't draw it */
392 XMoveResizeWindow(ob_display, p->text, textx, texty, textw, texth);
393 p->a_text->surface.parentx = textx;
394 p->a_text->surface.parenty = texty;
395 }
396
397 /* find the focused target */
398 for (i = 0, it = p->targets; it; ++i, it = g_list_next(it)) {
399 const ObFocusCyclePopupTarget *target = it->data;
400 const gint row = i / icons_per_row; /* starting from 0 */
401 const gint col = i % icons_per_row; /* starting from 0 */
402
403 if (target->client == c) {
404 /* save the target */
405 newtarget = target;
406 newtargetx = icons_center_x + l + (col * ICON_SIZE);
407 newtargety = t + (row * ICON_SIZE);
408
409 if (!p->mapped)
410 break; /* if we're not dimensioning, then we're done */
411 }
412 }
413
414 g_assert(newtarget != NULL);
415
416 /* create the hilite under the target icon */
417 {
418 RrPixel32 color;
419 gint i, j, o;
420
421 color = ((ob_rr_theme->osd_color->r & 0xff) << RrDefaultRedOffset) +
422 ((ob_rr_theme->osd_color->g & 0xff) << RrDefaultGreenOffset) +
423 ((ob_rr_theme->osd_color->b & 0xff) << RrDefaultBlueOffset);
424
425 o = 0;
426 for (i = 0; i < rgbah; ++i)
427 for (j = 0; j < rgbaw; ++j) {
428 guchar a;
429 const gint x = j + rgbax - newtargetx;
430 const gint y = i + rgbay - newtargety;
431
432 if (x < 0 || x >= ICON_SIZE ||
433 y < 0 || y >= ICON_SIZE)
434 {
435 /* outside the target */
436 a = 0x00;
437 }
438 else if (x < ICON_HILITE_WIDTH ||
439 x >= ICON_SIZE - ICON_HILITE_WIDTH ||
440 y < ICON_HILITE_WIDTH ||
441 y >= ICON_SIZE - ICON_HILITE_WIDTH)
442 {
443 /* the border of the target */
444 a = 0x88;
445 }
446 else {
447 /* the background of the target */
448 a = 0x22;
449 }
450
451 p->hilite_rgba[o++] =
452 color + (a << RrDefaultAlphaOffset);
453 }
454 }
455
456 /* * * draw everything * * */
457
458 /* draw the background */
459 RrPaint(p->a_bg, p->bg, w, h);
460
461 /* draw the icons */
462 for (i = 0, it = p->targets; it; ++i, it = g_list_next(it)) {
463 const ObFocusCyclePopupTarget *target = it->data;
464
465 /* have to redraw the targetted icon and last targetted icon,
466 they can pick up the hilite changes in the backgroud */
467 if (!p->mapped || newtarget == target || p->last_target == target) {
468 const gint row = i / icons_per_row; /* starting from 0 */
469 const gint col = i % icons_per_row; /* starting from 0 */
470 gint innerx, innery;
471
472 /* find the dimensions of the icon inside it */
473 innerx = icons_center_x + l + (col * ICON_SIZE);
474 innerx += ICON_HILITE_WIDTH + ICON_HILITE_MARGIN;
475 innery = t + (row * ICON_SIZE);
476 innery += ICON_HILITE_WIDTH + ICON_HILITE_MARGIN;
477
478 /* move the icon */
479 XMoveResizeWindow(ob_display, target->win,
480 innerx, innery, innerw, innerh);
481
482 /* get the icon from the client */
483 p->a_icon->texture[0].data.image.alpha =
484 target->client->iconic ? OB_ICONIC_ALPHA : 0xff;
485 p->a_icon->texture[0].data.image.image = target->icon;
486
487 /* draw the icon */
488 p->a_icon->surface.parentx = innerx;
489 p->a_icon->surface.parenty = innery;
490 RrPaint(p->a_icon, target->win, innerw, innerh);
491 }
492 }
493
494 /* draw the text */
495 p->a_text->texture[0].data.text.string = newtarget->text;
496 p->a_text->surface.parentx = textx;
497 p->a_text->surface.parenty = texty;
498 RrPaint(p->a_text, p->text, textw, texth);
499
500 p->last_target = newtarget;
501
502 g_free(screen_area);
503 }
504
505 void focus_cycle_popup_show(ObClient *c, gboolean iconic_windows,
506 gboolean all_desktops, gboolean dock_windows,
507 gboolean desktop_windows)
508 {
509 g_assert(c != NULL);
510
511 /* do this stuff only when the dialog is first showing */
512 if (!popup.mapped)
513 popup_setup(&popup, TRUE, FALSE);
514 g_assert(popup.targets != NULL);
515
516 popup_render(&popup, c);
517
518 if (!popup.mapped) {
519 /* show the dialog */
520 XMapWindow(ob_display, popup.bg);
521 XFlush(ob_display);
522 popup.mapped = TRUE;
523 screen_hide_desktop_popup();
524 }
525 }
526
527 void focus_cycle_popup_hide(void)
528 {
529 gulong ignore_start;
530
531 ignore_start = event_start_ignore_all_enters();
532
533 XUnmapWindow(ob_display, popup.bg);
534 XFlush(ob_display);
535
536 event_end_ignore_all_enters(ignore_start);
537
538 popup.mapped = FALSE;
539
540 popup_cleanup();
541
542 g_free(popup.hilite_rgba);
543 popup.hilite_rgba = NULL;
544 }
545
546 void focus_cycle_popup_single_show(struct _ObClient *c,
547 gboolean iconic_windows,
548 gboolean all_desktops,
549 gboolean dock_windows,
550 gboolean desktop_windows)
551 {
552 gchar *text;
553
554 g_assert(c != NULL);
555
556 /* do this stuff only when the dialog is first showing */
557 if (!popup.mapped) {
558 Rect *a;
559
560 popup_setup(&popup, FALSE, FALSE);
561 g_assert(popup.targets == NULL);
562
563 /* position the popup */
564 a = screen_physical_area_primary(FALSE);
565 icon_popup_position(single_popup, CenterGravity,
566 a->x + a->width / 2, a->y + a->height / 2);
567 icon_popup_height(single_popup, POPUP_HEIGHT);
568 icon_popup_min_width(single_popup, POPUP_WIDTH);
569 icon_popup_max_width(single_popup, MAX(a->width/3, POPUP_WIDTH));
570 icon_popup_text_width(single_popup, popup.maxtextw);
571 g_free(a);
572 }
573
574 text = popup_get_name(c);
575 icon_popup_show(single_popup, text, client_icon(c));
576 g_free(text);
577 screen_hide_desktop_popup();
578 }
579
580 void focus_cycle_popup_single_hide(void)
581 {
582 icon_popup_hide(single_popup);
583 }
584
585 gboolean focus_cycle_popup_is_showing(ObClient *c)
586 {
587 if (popup.mapped) {
588 GList *it;
589
590 for (it = popup.targets; it; it = g_list_next(it)) {
591 ObFocusCyclePopupTarget *t = it->data;
592 if (t->client == c)
593 return TRUE;
594 }
595 }
596 return FALSE;
597 }
598
599 static ObClient* popup_revert(ObClient *target)
600 {
601 GList *it, *itt;
602
603 for (it = popup.targets; it; it = g_list_next(it)) {
604 ObFocusCyclePopupTarget *t = it->data;
605 if (t->client == target) {
606 /* move to a previous window if possible */
607 for (itt = it->prev; itt; itt = g_list_previous(itt)) {
608 ObFocusCyclePopupTarget *t2 = it->data;
609 if (focus_cycle_valid(t2->client))
610 return t2->client;
611 }
612
613 /* otherwise move to a following window if possible */
614 for (itt = it->next; itt; itt = g_list_next(itt)) {
615 ObFocusCyclePopupTarget *t2 = it->data;
616 if (focus_cycle_valid(t2->client))
617 return t2->client;
618 }
619
620 /* otherwise, we can't go anywhere there is nowhere valid to go */
621 return NULL;
622 }
623 }
624 return NULL;
625 }
626
627 ObClient* focus_cycle_popup_refresh(ObClient *target,
628 gboolean redraw)
629 {
630 if (!popup.mapped) return NULL;
631
632 if (!focus_cycle_valid(target))
633 target = popup_revert(target);
634
635 redraw = popup_setup(&popup, TRUE, TRUE) && redraw;
636
637 if (!target && popup.targets)
638 target = ((ObFocusCyclePopupTarget*)popup.targets->data)->client;
639
640 if (target && redraw) {
641 popup.mapped = FALSE;
642 popup_render(&popup, target);
643 XFlush(ob_display);
644 popup.mapped = TRUE;
645 }
646
647 return target;
648 }
This page took 0.067255 seconds and 4 git commands to generate.