add anotehr stacking_add function.
[mikachu/openbox.git] / openbox / slit.c
1 #include "slit.h"
2 #include "screen.h"
3 #include "grab.h"
4 #include "openbox.h"
5 #include "render/theme.h"
6
7 #define SLIT_EVENT_MASK (ButtonPressMask | ButtonReleaseMask | \
8                          EnterWindowMask | LeaveWindowMask)
9 #define SLITAPP_EVENT_MASK (StructureNotifyMask)
10
11 GHashTable *slit_map = NULL;
12 GHashTable *slit_app_map = NULL;
13
14 static Slit *slit;
15 static int nslits;
16
17 static guint slit_hide_timeout = 3000; /* XXX make a config option */
18
19 static void slit_configure(Slit *self);
20
21 void slit_startup()
22 {
23     XSetWindowAttributes attrib;
24     int i;
25
26     slit_map = g_hash_table_new(g_int_hash, g_int_equal);
27     slit_app_map = g_hash_table_new(g_int_hash, g_int_equal);
28
29     nslits = 1;
30     slit = g_new0(struct Slit, nslits);
31     slit->obwin.type = Window_Slit;
32
33     for (i = 0; i < nslits; ++i) {
34         slit[i].horz = FALSE;
35         slit[i].hide = FALSE;
36         slit[i].hidden = TRUE;
37         slit[i].pos = SlitPos_TopRight;
38         slit[i].layer = Layer_Top;
39
40         attrib.event_mask = SLIT_EVENT_MASK;
41         attrib.override_redirect = True;
42         slit[i].frame = XCreateWindow(ob_display, ob_root, 0, 0, 1, 1, 0,
43                                       render_depth, InputOutput, render_visual,
44                                       CWOverrideRedirect | CWEventMask,
45                                       &attrib);
46         slit[i].a_frame = appearance_copy(theme_a_unfocused_title);
47         XSetWindowBorder(ob_display, slit[i].frame, theme_b_color->pixel);
48         XSetWindowBorderWidth(ob_display, slit[i].frame, theme_bwidth);
49
50         g_hash_table_insert(slit_map, &slit[i].frame, &slit[i]);
51         stacking_add(SLIT_AS_WINDOW(&slit[i]));
52         stacking_raise(SLIT_AS_WINDOW(&slit[i]));
53     }
54 }
55
56 void slit_shutdown()
57 {
58     int i;
59
60     for (i = 0; i < nslits; ++i) {
61         XDestroyWindow(ob_display, slit[i].frame);
62         appearance_free(slit[i].a_frame);
63         g_hash_table_remove(slit_map, &slit[i].frame);
64         stacking_remove(&slit[i]);
65     }
66     g_hash_table_destroy(slit_app_map);
67     g_hash_table_destroy(slit_map);
68 }
69
70 void slit_add(Window win, XWMHints *wmhints)
71 {
72     Slit *s;
73     SlitApp *app;
74     XWindowAttributes attrib;
75
76     /* XXX pick a slit */
77     s = &slit[0];
78
79     app = g_new0(SlitApp, 1);
80     app->slit = s;
81     app->win = win;
82     app->icon_win = (wmhints->flags & IconWindowHint) ?
83         wmhints->icon_window : win;
84     
85     if (XGetWindowAttributes(ob_display, app->icon_win, &attrib)) {
86         app->w = attrib.width;
87         app->h = attrib.height;
88     } else {
89         app->w = app->h = 64;
90     }
91
92     s->slit_apps = g_list_append(s->slit_apps, app);
93     slit_configure(s);
94
95     XReparentWindow(ob_display, app->icon_win, s->frame, app->x, app->y);
96     /*
97       This is the same case as in frame.c for client windows. When Openbox is
98       starting, the window is already mapped so we see unmap events occur for
99       it. There are 2 unmap events generated that we see, one with the 'event'
100       member set the root window, and one set to the client, but both get
101       handled and need to be ignored.
102     */
103     if (ob_state == State_Starting)
104         app->ignore_unmaps += 2;
105
106     if (app->win != app->icon_win) {
107         /* have to map it so that it can be re-managed on a restart */
108         XMoveWindow(ob_display, app->win, -1000, -1000);
109         XMapWindow(ob_display, app->win);
110     }
111     XMapWindow(ob_display, app->icon_win);
112     XSync(ob_display, False);
113
114     /* specify that if we exit, the window should not be destroyed and should
115        be reparented back to root automatically */
116     XChangeSaveSet(ob_display, app->icon_win, SetModeInsert);
117     XSelectInput(ob_display, app->icon_win, SLITAPP_EVENT_MASK);
118
119     grab_button_full(2, 0, app->icon_win,
120                      ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
121                      GrabModeAsync, ob_cursors.move);
122
123     g_hash_table_insert(slit_app_map, &app->icon_win, app);
124
125     g_message("Managed Slit App: 0x%lx", app->icon_win);
126 }
127
128 void slit_remove_all()
129 {
130     int i;
131
132     for (i = 0; i < nslits; ++i)
133         while (slit[i].slit_apps)
134             slit_remove(slit[i].slit_apps->data, TRUE);
135 }
136
137 void slit_remove(SlitApp *app, gboolean reparent)
138 {
139     ungrab_button(2, 0, app->icon_win);
140     XSelectInput(ob_display, app->icon_win, NoEventMask);
141     /* remove the window from our save set */
142     XChangeSaveSet(ob_display, app->icon_win, SetModeDelete);
143     XSync(ob_display, False);
144
145     g_hash_table_remove(slit_app_map, &app->icon_win);
146
147     if (reparent)
148         XReparentWindow(ob_display, app->icon_win, ob_root, app->x, app->y);
149
150     app->slit->slit_apps = g_list_remove(app->slit->slit_apps, app);
151     slit_configure(app->slit);
152
153     g_message("Unmanaged Slit App: 0x%lx", app->icon_win);
154
155     g_free(app);
156 }
157
158 void slit_configure_all()
159 {
160     int i; for (i = 0; i < nslits; ++i) slit_configure(&slit[i]);
161 }
162
163 static void slit_configure(Slit *self)
164 {
165     GList *it;
166     int spot;
167
168     self->w = self->h = spot = 0;
169
170     for (it = self->slit_apps; it; it = it->next) {
171         struct SlitApp *app = it->data;
172         if (self->horz) {
173             app->x = spot;
174             app->y = 0;
175             self->w += app->w;
176             self->h = MAX(self->h, app->h);
177             spot += app->w;
178         } else {
179             app->x = 0;
180             app->y = spot;
181             self->w = MAX(self->w, app->w);
182             self->h += app->h;
183             spot += app->h;
184         }
185
186         XMoveWindow(ob_display, app->icon_win, app->x, app->y);
187     }
188
189     /* used for calculating offsets */
190     self->w += theme_bwidth * 2;
191     self->h += theme_bwidth * 2;
192
193     /* calculate position */
194     switch (self->pos) {
195     case SlitPos_Floating:
196         self->x = self->user_x;
197         self->y = self->user_y;
198         break;
199     case SlitPos_TopLeft:
200         self->x = 0;
201         self->y = 0;
202         self->gravity = NorthWestGravity;
203         break;
204     case SlitPos_Top:
205         self->x = screen_physical_size.width / 2;
206         self->y = 0;
207         self->gravity = NorthGravity;
208         break;
209     case SlitPos_TopRight:
210         self->x = screen_physical_size.width;
211         self->y = 0;
212         self->gravity = NorthEastGravity;
213         break;
214     case SlitPos_Left:
215         self->x = 0;
216         self->y = screen_physical_size.height / 2;
217         self->gravity = WestGravity;
218         break;
219     case SlitPos_Right:
220         self->x = screen_physical_size.width;
221         self->y = screen_physical_size.height / 2;
222         self->gravity = EastGravity;
223         break;
224     case SlitPos_BottomLeft:
225         self->x = 0;
226         self->y = screen_physical_size.height;
227         self->gravity = SouthWestGravity;
228         break;
229     case SlitPos_Bottom:
230         self->x = screen_physical_size.width / 2;
231         self->y = screen_physical_size.height;
232         self->gravity = SouthGravity;
233         break;
234     case SlitPos_BottomRight:
235         self->x = screen_physical_size.width;
236         self->y = screen_physical_size.height;
237         self->gravity = SouthEastGravity;
238         break;
239     }
240
241     switch(self->gravity) {
242     case NorthGravity:
243     case CenterGravity:
244     case SouthGravity:
245         self->x -= self->w / 2;
246         break;
247     case NorthEastGravity:
248     case EastGravity:
249     case SouthEastGravity:
250         self->x -= self->w;
251         break;
252     }
253     switch(self->gravity) {
254     case WestGravity:
255     case CenterGravity:
256     case EastGravity:
257         self->y -= self->h / 2;
258         break;
259     case SouthWestGravity:
260     case SouthGravity:
261     case SouthEastGravity:
262         self->y -= self->h;
263         break;
264     }
265
266     if (self->hide && self->hidden) {
267         g_message("hidden");
268         switch (self->pos) {
269         case SlitPos_Floating:
270             break;
271         case SlitPos_TopLeft:
272             if (self->horz)
273                 self->y -= self->h - theme_bwidth;
274             else
275                 self->x -= self->w - theme_bwidth;
276             break;
277         case SlitPos_Top:
278             self->y -= self->h - theme_bwidth;
279             break;
280         case SlitPos_TopRight:
281             if (self->horz)
282                 self->y -= self->h - theme_bwidth;
283             else
284                 self->x += self->w - theme_bwidth;
285             break;
286         case SlitPos_Left:
287             self->x -= self->w - theme_bwidth;
288             break;
289         case SlitPos_Right:
290             self->x += self->w - theme_bwidth;
291             break;
292         case SlitPos_BottomLeft:
293             if (self->horz)
294                 self->y += self->h - theme_bwidth;
295             else
296                 self->x -= self->w - theme_bwidth;
297             break;
298         case SlitPos_Bottom:
299             self->y += self->h - theme_bwidth;
300             break;
301         case SlitPos_BottomRight:
302             if (self->horz)
303                 self->y += self->h - theme_bwidth;
304             else
305                 self->x += self->w - theme_bwidth;
306             break;
307         }    
308     }
309
310     /* not used for actually sizing shit */
311     self->w -= theme_bwidth * 2;
312     self->h -= theme_bwidth * 2;
313
314     if (self->w > 0 && self->h > 0) {
315         RECT_SET(self->a_frame->area, 0, 0, self->w, self->h);
316         XMoveResizeWindow(ob_display, self->frame,
317                           self->x, self->y, self->w, self->h);
318
319         paint(self->frame, self->a_frame);
320         XMapWindow(ob_display, self->frame);
321     } else
322         XUnmapWindow(ob_display, self->frame);
323
324     /* but they are useful outside of this function! */
325     self->w += theme_bwidth * 2;
326     self->h += theme_bwidth * 2;
327 }
328
329 void slit_app_configure(SlitApp *app, int w, int h)
330 {
331     app->w = w;
332     app->h = h;
333     slit_configure(app->slit);
334 }
335
336 void slit_app_drag(SlitApp *app, XMotionEvent *e)
337 {
338     Slit *src, *dest = NULL;
339     SlitApp *over = NULL;
340     GList *it;
341     int i;
342     int x, y;
343     gboolean after;
344
345     src = app->slit;
346     x = e->x_root;
347     y = e->y_root;
348
349     /* which slit are we on top of? */
350     for (i = 0; i < nslits; ++i)
351         if (x >= slit[i].x &&
352             y >= slit[i].y &&
353             x < slit[i].x + slit[i].w &&
354             y < slit[i].y + slit[i].h) {
355             dest = &slit[i];
356             break;
357         }
358     if (!dest) return;
359
360     x -= dest->x;
361     y -= dest->y;
362
363     /* which slit app are we on top of? */
364     for (it = dest->slit_apps; it; it = it->next) {
365         over = it->data;
366         if (dest->horz) {
367             if (x >= over->x && x < over->x + over->w)
368                 break;
369         } else {
370             if (y >= over->y && y < over->y + over->h)
371                 break;
372         }
373     }
374     if (!it || app == over) return;
375
376     x -= over->x;
377     y -= over->y;
378
379     if (dest->horz)
380         after = (x > over->w / 2);
381     else
382         after = (y > over->h / 2);
383
384     /* remove before doing the it->next! */
385     src->slit_apps = g_list_remove(src->slit_apps, app);
386     if (src != dest) slit_configure(src);
387
388     if (after) it = it->next;
389
390     dest->slit_apps = g_list_insert_before(dest->slit_apps, it, app);
391     slit_configure(dest);
392 }
393
394 static void hide_timeout(Slit *self)
395 {
396     /* dont repeat */
397     timer_stop(self->hide_timer);
398     self->hide_timer = NULL;
399
400     /* hide */
401     self->hidden = TRUE;
402     slit_configure(self);
403 }
404
405 void slit_hide(Slit *self, gboolean hide)
406 {
407     if (self->hidden == hide || !self->hide)
408         return;
409     if (!hide) {
410         /* show */
411         self->hidden = FALSE;
412         slit_configure(self);
413
414         /* if was hiding, stop it */
415         if (self->hide_timer) {
416             timer_stop(self->hide_timer);
417             self->hide_timer = NULL;
418         }
419     } else {
420         g_assert(!self->hide_timer);
421         self->hide_timer = timer_start(slit_hide_timeout * 1000,
422                                        (TimeoutHandler)hide_timeout, self);
423     }
424 }