add an option to next/previous window to only include hilited/flashing/urgent windows...
[dana/openbox.git] / openbox / focus_cycle.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    focus_cycle.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.h"
21 #include "focus_cycle_indicator.h"
22 #include "client.h"
23 #include "frame.h"
24 #include "focus.h"
25 #include "screen.h"
26 #include "openbox.h"
27 #include "debug.h"
28
29 #include <X11/Xlib.h>
30 #include <glib.h>
31
32 typedef enum {
33     OB_CYCLE_NONE = 0,
34     OB_CYCLE_NORMAL,
35     OB_CYCLE_DIRECTIONAL
36 } ObCycleType;
37
38 ObClient       *focus_cycle_target = NULL;
39 static ObCycleType focus_cycle_type = OB_CYCLE_NONE;
40 static gboolean focus_cycle_iconic_windows;
41 static gboolean focus_cycle_all_desktops;
42 static gboolean focus_cycle_nonhilite_windows;
43 static gboolean focus_cycle_dock_windows;
44 static gboolean focus_cycle_desktop_windows;
45
46 static ObClient *focus_find_directional(ObClient *c,
47                                         ObDirection dir,
48                                         gboolean dock_windows,
49                                         gboolean desktop_windows);
50
51 void focus_cycle_startup(gboolean reconfig)
52 {
53     if (reconfig) return;
54 }
55
56 void focus_cycle_shutdown(gboolean reconfig)
57 {
58     if (reconfig) return;
59 }
60
61 void focus_cycle_addremove(ObClient *c, gboolean redraw)
62 {
63     if (!focus_cycle_type)
64         return;
65
66     if (focus_cycle_type == OB_CYCLE_DIRECTIONAL) {
67         if (c && focus_cycle_target == c) {
68             focus_directional_cycle(0, TRUE, TRUE, TRUE, TRUE,
69                                     TRUE, TRUE, TRUE);
70         }
71     }
72     else if (c && redraw) {
73         gboolean v, s;
74
75         v = focus_cycle_valid(c);
76         s = focus_cycle_popup_is_showing(c);
77
78         if (v != s)
79             focus_cycle_reorder();
80     }
81     else if (redraw) {
82         focus_cycle_reorder();
83     }
84 }
85
86 void focus_cycle_reorder()
87 {
88     if (focus_cycle_type == OB_CYCLE_NORMAL) {
89         focus_cycle_target = focus_cycle_popup_refresh(focus_cycle_target,
90                                                        TRUE);
91         focus_cycle_update_indicator(focus_cycle_target);
92         if (!focus_cycle_target)
93             focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,
94                         TRUE, TRUE, TRUE, TRUE, TRUE);
95     }
96 }
97
98 ObClient* focus_cycle(gboolean forward, gboolean all_desktops,
99                       gboolean nonhilite_windows,
100                       gboolean dock_windows, gboolean desktop_windows,
101                       gboolean linear, gboolean interactive,
102                       gboolean showbar, ObFocusCyclePopupMode mode,
103                       gboolean done, gboolean cancel)
104 {
105     static GList *order = NULL;
106     GList *it, *start, *list;
107     ObClient *ft = NULL;
108     ObClient *ret = NULL;
109
110     if (interactive) {
111         if (cancel) {
112             focus_cycle_target = NULL;
113             goto done_cycle;
114         } else if (done)
115             goto done_cycle;
116
117         if (!focus_order)
118             goto done_cycle;
119
120         if (linear) list = client_list;
121         else        list = focus_order;
122     } else {
123         if (!focus_order)
124             goto done_cycle;
125         list = client_list;
126     }
127
128     if (focus_cycle_target == NULL) {
129         focus_cycle_iconic_windows = TRUE;
130         focus_cycle_all_desktops = all_desktops;
131         focus_cycle_nonhilite_windows = nonhilite_windows;
132         focus_cycle_dock_windows = dock_windows;
133         focus_cycle_desktop_windows = desktop_windows;
134         start = it = g_list_find(list, focus_client);
135     } else
136         start = it = g_list_find(list, focus_cycle_target);
137
138     if (!start) /* switched desktops or something? */
139         start = it = forward ? g_list_last(list) : g_list_first(list);
140     if (!start) goto done_cycle;
141
142     do {
143         if (forward) {
144             it = it->next;
145             if (it == NULL) it = g_list_first(list);
146         } else {
147             it = it->prev;
148             if (it == NULL) it = g_list_last(list);
149         }
150         ft = it->data;
151         if (focus_cycle_valid(ft)) {
152             if (interactive) {
153                 if (ft != focus_cycle_target) { /* prevents flicker */
154                     focus_cycle_target = ft;
155                     focus_cycle_type = OB_CYCLE_NORMAL;
156                     focus_cycle_draw_indicator(showbar ? ft : NULL);
157                 }
158                 /* same arguments as focus_target_valid */
159                 focus_cycle_popup_show(ft, mode);
160                 return focus_cycle_target;
161             } else if (ft != focus_cycle_target) {
162                 focus_cycle_target = ft;
163                 focus_cycle_type = OB_CYCLE_NORMAL;
164                 done = TRUE;
165                 break;
166             }
167         }
168     } while (it != start);
169
170 done_cycle:
171     if (done && !cancel) ret = focus_cycle_target;
172
173     focus_cycle_target = NULL;
174     focus_cycle_type = OB_CYCLE_NONE;
175     g_list_free(order);
176     order = NULL;
177
178     if (interactive) {
179         focus_cycle_draw_indicator(NULL);
180         focus_cycle_popup_hide();
181     }
182
183     return ret;
184 }
185
186 /* this be mostly ripped from fvwm */
187 static ObClient *focus_find_directional(ObClient *c, ObDirection dir,
188                                         gboolean dock_windows,
189                                         gboolean desktop_windows)
190 {
191     gint my_cx, my_cy, his_cx, his_cy;
192     gint offset = 0;
193     gint distance = 0;
194     gint score, best_score;
195     ObClient *best_client, *cur;
196     GList *it;
197
198     if (!client_list)
199         return NULL;
200
201     /* first, find the centre coords of the currently focused window */
202     my_cx = c->frame->area.x + c->frame->area.width / 2;
203     my_cy = c->frame->area.y + c->frame->area.height / 2;
204
205     best_score = -1;
206     best_client = c;
207
208     for (it = g_list_first(client_list); it; it = g_list_next(it)) {
209         cur = it->data;
210
211         /* the currently selected window isn't interesting */
212         if (cur == c)
213             continue;
214         if (!focus_cycle_valid(it->data))
215             continue;
216
217         /* find the centre coords of this window, from the
218          * currently focused window's point of view */
219         his_cx = (cur->frame->area.x - my_cx)
220             + cur->frame->area.width / 2;
221         his_cy = (cur->frame->area.y - my_cy)
222             + cur->frame->area.height / 2;
223
224         if (dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
225             dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST)
226         {
227             gint tx;
228             /* Rotate the diagonals 45 degrees counterclockwise.
229              * To do this, multiply the matrix /+h +h\ with the
230              * vector (x y).                   \-h +h/
231              * h = sqrt(0.5). We can set h := 1 since absolute
232              * distance doesn't matter here. */
233             tx = his_cx + his_cy;
234             his_cy = -his_cx + his_cy;
235             his_cx = tx;
236         }
237
238         switch (dir) {
239         case OB_DIRECTION_NORTH:
240         case OB_DIRECTION_SOUTH:
241         case OB_DIRECTION_NORTHEAST:
242         case OB_DIRECTION_SOUTHWEST:
243             offset = (his_cx < 0) ? -his_cx : his_cx;
244             distance = ((dir == OB_DIRECTION_NORTH ||
245                          dir == OB_DIRECTION_NORTHEAST) ?
246                         -his_cy : his_cy);
247             break;
248         case OB_DIRECTION_EAST:
249         case OB_DIRECTION_WEST:
250         case OB_DIRECTION_SOUTHEAST:
251         case OB_DIRECTION_NORTHWEST:
252             offset = (his_cy < 0) ? -his_cy : his_cy;
253             distance = ((dir == OB_DIRECTION_WEST ||
254                          dir == OB_DIRECTION_NORTHWEST) ?
255                         -his_cx : his_cx);
256             break;
257         }
258
259         /* the target must be in the requested direction */
260         if (distance <= 0)
261             continue;
262
263         /* Calculate score for this window.  The smaller the better. */
264         score = distance + offset;
265
266         /* windows more than 45 degrees off the direction are
267          * heavily penalized and will only be chosen if nothing
268          * else within a million pixels */
269         if (offset > distance)
270             score += 1000000;
271
272         if (best_score == -1 || score < best_score) {
273             best_client = cur;
274             best_score = score;
275         }
276     }
277
278     return best_client;
279 }
280
281 ObClient* focus_directional_cycle(ObDirection dir, gboolean dock_windows,
282                                   gboolean desktop_windows,
283                                   gboolean interactive,
284                                   gboolean showbar, gboolean dialog,
285                                   gboolean done, gboolean cancel)
286 {
287     static ObClient *first = NULL;
288     ObClient *ft = NULL;
289     ObClient *ret = NULL;
290
291     if (cancel) {
292         focus_cycle_target = NULL;
293         goto done_cycle;
294     } else if (done && interactive)
295         goto done_cycle;
296
297     if (!focus_order)
298         goto done_cycle;
299
300     if (focus_cycle_target == NULL) {
301         focus_cycle_iconic_windows = FALSE;
302         focus_cycle_all_desktops = FALSE;
303         focus_cycle_nonhilite_windows = TRUE;
304         focus_cycle_dock_windows = dock_windows;
305         focus_cycle_desktop_windows = desktop_windows;
306     }
307
308     if (!first) first = focus_client;
309
310     if (focus_cycle_target)
311         ft = focus_find_directional(focus_cycle_target, dir, dock_windows,
312                                     desktop_windows);
313     else if (first)
314         ft = focus_find_directional(first, dir, dock_windows, desktop_windows);
315     else {
316         GList *it;
317
318         for (it = focus_order; it; it = g_list_next(it))
319             if (focus_cycle_valid(it->data)) {
320                 ft = it->data;
321                 break;
322             }
323     }
324
325     if (ft && ft != focus_cycle_target) {/* prevents flicker */
326         focus_cycle_target = ft;
327         focus_cycle_type = OB_CYCLE_DIRECTIONAL;
328         if (!interactive)
329             goto done_cycle;
330         focus_cycle_draw_indicator(showbar ? ft : NULL);
331     }
332     if (focus_cycle_target && dialog)
333         /* same arguments as focus_target_valid */
334         focus_cycle_popup_single_show(focus_cycle_target);
335     return focus_cycle_target;
336
337 done_cycle:
338     if (done && !cancel) ret = focus_cycle_target;
339
340     first = NULL;
341     focus_cycle_target = NULL;
342     focus_cycle_type = OB_CYCLE_NONE;
343
344     focus_cycle_draw_indicator(NULL);
345     focus_cycle_popup_single_hide();
346
347     return ret;
348 }
349
350 gboolean focus_cycle_valid(struct _ObClient *client)
351 {
352     return focus_valid_target(client, screen_desktop, TRUE,
353                               focus_cycle_iconic_windows,
354                               focus_cycle_all_desktops,
355                               focus_cycle_nonhilite_windows,
356                               focus_cycle_dock_windows,
357                               focus_cycle_desktop_windows,
358                               FALSE);
359 }