fix no more crashing in the client menu.
[dana/openbox.git] / openbox / client_menu.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    client_menu.c for the Openbox window manager
4    Copyright (c) 2003-2007   Dana Jansens
5
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.
10
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.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "debug.h"
20 #include "menu.h"
21 #include "menuframe.h"
22 #include "screen.h"
23 #include "client.h"
24 #include "openbox.h"
25 #include "frame.h"
26 #include "moveresize.h"
27 #include "prop.h"
28 #include "gettext.h"
29
30 #include <glib.h>
31
32 #define CLIENT_MENU_NAME  "client-menu"
33 #define SEND_TO_MENU_NAME "client-send-to-menu"
34 #define LAYER_MENU_NAME   "client-layer-menu"
35
36 enum {
37     LAYER_TOP,
38     LAYER_NORMAL,
39     LAYER_BOTTOM
40 };
41
42 enum {
43     CLIENT_SEND_TO,
44     CLIENT_LAYER,
45     CLIENT_ICONIFY,
46     CLIENT_RESTORE,
47     CLIENT_MAXIMIZE,
48     CLIENT_SHADE,
49     CLIENT_DECORATE,
50     CLIENT_MOVE,
51     CLIENT_RESIZE,
52     CLIENT_CLOSE
53 };
54
55 static gboolean client_menu_update(ObMenuFrame *frame, gpointer data)
56 {
57     ObMenu *menu = frame->menu;
58     GList *it;
59
60     if (frame->client == NULL || !client_normal(frame->client))
61         return FALSE; /* don't show the menu */
62
63     for (it = menu->entries; it; it = g_list_next(it)) {
64         ObMenuEntry *e = it->data;
65         gboolean *en = &e->data.normal.enabled; /* save some typing */
66         ObClient *c = frame->client;
67
68         if (e->type == OB_MENU_ENTRY_TYPE_NORMAL) {
69             switch (e->id) {
70             case CLIENT_ICONIFY:
71                 *en = c->functions & OB_CLIENT_FUNC_ICONIFY;
72                 break;
73             case CLIENT_RESTORE:
74                 *en = c->max_horz || c->max_vert;
75                 break;
76             case CLIENT_MAXIMIZE:
77                 *en = ((c->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
78                        (!c->max_horz || !c->max_vert));
79                 break;
80             case CLIENT_SHADE:
81                 *en = c->functions & OB_CLIENT_FUNC_SHADE;
82                 break;
83             case CLIENT_MOVE:
84                 *en = c->functions & OB_CLIENT_FUNC_MOVE;
85                 break;
86             case CLIENT_RESIZE:
87                 *en = c->functions & OB_CLIENT_FUNC_RESIZE;
88                 break;
89             case CLIENT_CLOSE:
90                 *en = c->functions & OB_CLIENT_FUNC_CLOSE;
91                 break;
92             case CLIENT_DECORATE:
93                 *en = client_normal(c);
94                 break;
95             default:
96                 *en = TRUE;
97             }
98         }
99     }
100     return TRUE; /* show the menu */
101 }
102
103 static void client_menu_execute(ObMenuEntry *e, ObMenuFrame *f,
104                                 ObClient *c, guint state, gpointer data,
105                                 Time time)
106 {
107     GList *it;
108
109     g_assert(c);
110
111     switch (e->id) {
112     case CLIENT_ICONIFY:
113         client_iconify(c, TRUE, FALSE);
114         /* the client won't be on screen anymore so hide the menu */
115         menu_frame_hide_all();
116         f = NULL; /* and don't update */
117         break;
118     case CLIENT_RESTORE:
119         client_maximize(c, FALSE, 0);
120         break;
121     case CLIENT_MAXIMIZE:
122         client_maximize(c, TRUE, 0);
123         break;
124     case CLIENT_SHADE:
125         client_shade(c, !c->shaded);
126         break;
127     case CLIENT_DECORATE:
128         client_set_undecorated(c, !c->undecorated);
129         break;
130     case CLIENT_MOVE:
131         moveresize_start(c,0,0,0, prop_atoms.net_wm_moveresize_move_keyboard);
132         break;
133     case CLIENT_RESIZE:
134         moveresize_start(c,0,0,0,prop_atoms.net_wm_moveresize_size_keyboard);
135         break;
136     case CLIENT_CLOSE:
137         client_close(c);
138         break;
139     default:
140         g_assert_not_reached();
141     }
142
143     /* update the menu cuz stuff can have changed */
144     if (f) {
145         client_menu_update(f, NULL);
146         menu_frame_render(f);
147     }
148 }
149
150 static gboolean layer_menu_update(ObMenuFrame *frame, gpointer data)
151 {
152     ObMenu *menu = frame->menu;
153     GList *it;
154
155     if (frame->client == NULL || !client_normal(frame->client))
156         return FALSE; /* don't show the menu */
157
158     for (it = menu->entries; it; it = g_list_next(it)) {
159         ObMenuEntry *e = it->data;
160         gboolean *en = &e->data.normal.enabled; /* save some typing */
161         ObClient *c = frame->client;
162
163         if (e->type == OB_MENU_ENTRY_TYPE_NORMAL) {
164             switch (e->id) {
165             case LAYER_TOP:
166                 *en = !c->above;
167                 break;
168             case LAYER_NORMAL:
169                 *en = c->above || c->below;
170                 break;
171             case LAYER_BOTTOM:
172                 *en = !c->below;
173                 break;
174             default:
175                 *en = TRUE;
176             }
177         }
178     }
179     return TRUE; /* show the menu */
180 }
181
182 static void layer_menu_execute(ObMenuEntry *e, ObMenuFrame *f,
183                                ObClient *c, guint state, gpointer data,
184                                Time time)
185 {
186     g_assert(c);
187
188     switch (e->id) {
189     case LAYER_TOP:
190         client_set_layer(f->client, 1);
191         break;
192     case LAYER_NORMAL:
193         client_set_layer(f->client, 0);
194         break;
195     case LAYER_BOTTOM:
196         client_set_layer(f->client, -1);
197         break;
198     default:
199         g_assert_not_reached();
200     }
201
202     /* update the menu cuz stuff can have changed */
203     if (f) {
204         layer_menu_update(f, NULL);
205         menu_frame_render(f);
206     }
207 }
208
209 static gboolean send_to_menu_update(ObMenuFrame *frame, gpointer data)
210 {
211     ObMenu *menu = frame->menu;
212     guint i;
213     ObMenuEntry *e;
214
215     menu_clear_entries(menu);
216
217     if (frame->client == NULL || !client_normal(frame->client))
218         return FALSE; /* don't show the menu */
219
220     for (i = 0; i <= screen_num_desktops; ++i) {
221         const gchar *name;
222         guint desk;
223
224         if (i >= screen_num_desktops) {
225             menu_add_separator(menu, -1, NULL);
226
227             desk = DESKTOP_ALL;
228             name = _("All desktops");
229         } else {
230             desk = i;
231             name = screen_desktop_names[i];
232         }
233
234         e = menu_add_normal(menu, desk, name, NULL, FALSE);
235         e->id = desk;
236         if (desk == DESKTOP_ALL) {
237             e->data.normal.mask = ob_rr_theme->desk_mask;
238             e->data.normal.mask_normal_color = ob_rr_theme->menu_color;
239             e->data.normal.mask_selected_color =
240                 ob_rr_theme->menu_selected_color;
241             e->data.normal.mask_disabled_color =
242                 ob_rr_theme->menu_disabled_color;
243             e->data.normal.mask_disabled_selected_color =
244                 ob_rr_theme->menu_disabled_selected_color;
245         }
246
247         if (frame->client->desktop == desk)
248             e->data.normal.enabled = FALSE;
249     }
250     return TRUE; /* show the menu */
251 }
252
253 static void send_to_menu_execute(ObMenuEntry *e, ObMenuFrame *f,
254                                  ObClient *c, guint state, gpointer data,
255                                  Time time)
256 {
257     g_assert(c);
258
259     client_set_desktop(c, e->id, FALSE);
260     /* the client won't even be on the screen anymore, so hide the menu */
261     if (f)
262         menu_frame_hide_all();
263 }
264
265 static void client_menu_place(ObMenuFrame *frame, gint *x, gint *y,
266                               gint button, gpointer data)
267 {
268     gint dx, dy;
269
270     if (button == 0 && frame->client) {
271         *x = frame->client->frame->area.x;
272
273         /* try below the titlebar */
274         *y = frame->client->frame->area.y + frame->client->frame->size.top -
275             frame->client->frame->bwidth;
276         menu_frame_move_on_screen(frame, *x, *y, &dx, &dy);
277         if (dy != 0) {
278             /* try above the titlebar */
279             *y = frame->client->frame->area.y + frame->client->frame->bwidth -
280                 frame->area.height;
281             menu_frame_move_on_screen(frame, *x, *y, &dx, &dy);
282         }
283         if (dy != 0) {
284             /* didnt fit either way, use move on screen's values */
285             *y = frame->client->frame->area.y + frame->client->frame->size.top;
286             menu_frame_move_on_screen(frame, *x, *y, &dx, &dy);
287         }
288
289         *x += dx;
290         *y += dy;
291     } else {
292         gint myx, myy;
293
294         myx = *x;
295         myy = *y;
296
297         /* try to the bottom right of the cursor */
298         menu_frame_move_on_screen(frame, myx, myy, &dx, &dy);
299         if (dx != 0 || dy != 0) {
300             /* try to the bottom left of the cursor */
301             myx = *x - frame->area.width;
302             myy = *y;
303             menu_frame_move_on_screen(frame, myx, myy, &dx, &dy);
304         }
305         if (dx != 0 || dy != 0) {
306             /* try to the top right of the cursor */
307             myx = *x;
308             myy = *y - frame->area.height;
309             menu_frame_move_on_screen(frame, myx, myy, &dx, &dy);
310         }
311         if (dx != 0 || dy != 0) {
312             /* try to the top left of the cursor */
313             myx = *x - frame->area.width;
314             myy = *y - frame->area.height;
315             menu_frame_move_on_screen(frame, myx, myy, &dx, &dy);
316         }
317         if (dx != 0 || dy != 0) {
318             /* if didnt fit on either side so just use what it says */
319             myx = *x;
320             myy = *y;
321             menu_frame_move_on_screen(frame, myx, myy, &dx, &dy);
322         }
323         *x = myx + dx;
324         *y = myy + dy;
325     }
326 }
327
328 void client_menu_startup()
329 {
330     ObMenu *menu;
331     ObMenuEntry *e;
332
333     menu = menu_new(LAYER_MENU_NAME, _("&Layer"), TRUE, NULL);
334     menu_show_all_shortcuts(menu, TRUE);
335     menu_set_update_func(menu, layer_menu_update);
336     menu_set_execute_func(menu, layer_menu_execute);
337
338     menu_add_normal(menu, LAYER_TOP, _("Always on &top"), NULL, TRUE);
339     menu_add_normal(menu, LAYER_NORMAL, _("&Normal"), NULL, TRUE);
340     menu_add_normal(menu, LAYER_BOTTOM, _("Always on &bottom"),NULL, TRUE);
341
342
343     menu = menu_new(SEND_TO_MENU_NAME, _("&Send to desktop"), TRUE, NULL);
344     menu_set_update_func(menu, send_to_menu_update);
345     menu_set_execute_func(menu, send_to_menu_execute);
346
347     menu = menu_new(CLIENT_MENU_NAME, _("Client menu"), TRUE, NULL);
348     menu_show_all_shortcuts(menu, TRUE);
349     menu_set_update_func(menu, client_menu_update);
350     menu_set_place_func(menu, client_menu_place);
351     menu_set_execute_func(menu, client_menu_execute);
352
353     e = menu_add_normal(menu, CLIENT_RESTORE, _("R&estore"), NULL, TRUE);
354     e->data.normal.mask = ob_rr_theme->max_toggled_mask; 
355     e->data.normal.mask_normal_color = ob_rr_theme->menu_color;
356     e->data.normal.mask_selected_color = ob_rr_theme->menu_selected_color;
357     e->data.normal.mask_disabled_color = ob_rr_theme->menu_disabled_color;
358     e->data.normal.mask_disabled_selected_color =
359         ob_rr_theme->menu_disabled_selected_color;
360
361     menu_add_normal(menu, CLIENT_MOVE, _("&Move"), NULL, TRUE);
362
363     menu_add_normal(menu, CLIENT_RESIZE, _("Resi&ze"), NULL, TRUE);
364
365     e = menu_add_normal(menu, CLIENT_ICONIFY, _("Ico&nify"), NULL, TRUE);
366     e->data.normal.mask = ob_rr_theme->iconify_mask;
367     e->data.normal.mask_normal_color = ob_rr_theme->menu_color;
368     e->data.normal.mask_selected_color = ob_rr_theme->menu_selected_color;
369     e->data.normal.mask_disabled_color = ob_rr_theme->menu_disabled_color;
370     e->data.normal.mask_disabled_selected_color =
371         ob_rr_theme->menu_disabled_selected_color;
372
373     e = menu_add_normal(menu, CLIENT_MAXIMIZE, _("Ma&ximize"), NULL, TRUE);
374     e->data.normal.mask = ob_rr_theme->max_mask; 
375     e->data.normal.mask_normal_color = ob_rr_theme->menu_color;
376     e->data.normal.mask_selected_color = ob_rr_theme->menu_selected_color;
377     e->data.normal.mask_disabled_color = ob_rr_theme->menu_disabled_color;
378     e->data.normal.mask_disabled_selected_color =
379         ob_rr_theme->menu_disabled_selected_color;
380
381     e = menu_add_normal(menu, CLIENT_SHADE, _("&Roll up/down"), NULL, TRUE);
382     e->data.normal.mask = ob_rr_theme->shade_mask;
383     e->data.normal.mask_normal_color = ob_rr_theme->menu_color;
384     e->data.normal.mask_selected_color = ob_rr_theme->menu_selected_color;
385     e->data.normal.mask_disabled_color = ob_rr_theme->menu_disabled_color;
386     e->data.normal.mask_disabled_selected_color =
387         ob_rr_theme->menu_disabled_selected_color;
388
389     menu_add_normal(menu, CLIENT_DECORATE, _("Un/&Decorate"), NULL, TRUE);
390
391     menu_add_separator(menu, -1, NULL);
392
393     menu_add_submenu(menu, CLIENT_SEND_TO, SEND_TO_MENU_NAME);
394
395     menu_add_submenu(menu, CLIENT_LAYER, LAYER_MENU_NAME);
396
397     menu_add_separator(menu, -1, NULL);
398
399     e = menu_add_normal(menu, CLIENT_CLOSE, _("&Close"), NULL, TRUE);
400     e->data.normal.mask = ob_rr_theme->close_mask;
401     e->data.normal.mask_normal_color = ob_rr_theme->menu_color;
402     e->data.normal.mask_selected_color = ob_rr_theme->menu_selected_color;
403     e->data.normal.mask_disabled_color = ob_rr_theme->menu_disabled_color;
404     e->data.normal.mask_disabled_selected_color =
405         ob_rr_theme->menu_disabled_selected_color;
406 }