start of showing/rendering menus. woot!
[dana/openbox.git] / openbox / menu.c
1 #include "menu.h"
2 #include "openbox.h"
3 #include "render/theme.h"
4
5 GHashTable *menu_hash = NULL;
6
7 #define ENTRY_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
8                          ButtonPressMask | ButtonReleaseMask)
9
10 void menu_destroy_hash_key(gpointer data)
11 {
12     g_free(data);
13 }
14
15 void menu_free_entries(Menu *menu)
16 {
17     GList *it;
18
19     for (it = menu->entries; it; it = it->next)
20         menu_entry_free((MenuEntry *)it->data);
21
22     g_list_free(menu->entries);
23 }
24
25 void menu_destroy_hash_value(gpointer data)
26 {
27     Menu *del_menu = (Menu *)data;
28     MenuRenderData *rd = del_menu->render_data;
29
30     menu_free_entries(del_menu);
31
32     g_free(del_menu->label);
33     g_free(del_menu->name);
34
35     appearance_free(rd->a_title);
36     XDestroyWindow(ob_display, rd->title);
37     XDestroyWindow(ob_display, rd->frame);
38 }
39
40 void menu_entry_free(MenuEntry *entry)
41 {
42     g_free(entry->label);
43     g_free(entry->render_data);
44     action_free(entry->action);
45 }
46     
47 void menu_startup()
48 {
49     Menu *m;
50
51     menu_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
52                                       menu_destroy_hash_key,
53                                       menu_destroy_hash_value);
54     m = menu_new("sex menu", "root", NULL);
55     menu_add_entry(m, menu_entry_new("foo shit etc bleh",
56                                      action_from_string("restart")));
57     menu_add_entry(m, menu_entry_new("more shit",
58                                      action_from_string("restart")));
59     menu_add_entry(m, menu_entry_new("",
60                                      action_from_string("restart")));
61     menu_add_entry(m, menu_entry_new("and yet more",
62                                      action_from_string("restart")));
63 }
64
65 void menu_shutdown()
66 {
67     g_hash_table_destroy(menu_hash);
68 }
69
70 static Window createWindow(Window parent, unsigned long mask,
71                            XSetWindowAttributes *attrib)
72 {
73     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
74                          render_depth, InputOutput, render_visual,
75                          mask, attrib);
76                        
77 }
78
79 Menu *menu_new(char *label, char *name, Menu *parent)
80 {
81     XSetWindowAttributes attrib;
82     Menu *self;
83     MenuRenderData *data;
84
85     self = g_new0(Menu, 1);
86     self->label = g_strdup(label);
87     self->name = g_strdup(name);
88     self->parent = parent;
89
90     self->entries = NULL;
91     self->shown = FALSE;
92     self->invalid = FALSE;
93     /* default controllers? */
94
95     data = g_new(MenuRenderData, 1);
96
97     attrib.override_redirect = TRUE;
98     data->frame = createWindow(ob_root, CWOverrideRedirect, &attrib);
99     data->title = createWindow(data->frame, 0, &attrib);
100     data->items = createWindow(data->frame, 0, &attrib);
101
102     XSetWindowBorderWidth(ob_display, data->frame, theme_bwidth);
103     XSetWindowBorderWidth(ob_display, data->title, theme_bwidth);
104     XSetWindowBorder(ob_display, data->frame, theme_b_color->pixel);
105     XSetWindowBorder(ob_display, data->title, theme_b_color->pixel);
106
107     XMapWindow(ob_display, data->title);
108     XMapWindow(ob_display, data->items);
109
110     data->a_title = appearance_copy(theme_a_menu_title);
111     data->a_items = appearance_copy(theme_a_menu);
112
113     self->render_data = data;
114
115     g_hash_table_insert(menu_hash, g_strdup(name), self);
116     return self;
117 }
118
119 void menu_free(char *name)
120 {
121     g_hash_table_remove(menu_hash, name);
122 }
123
124 MenuEntry *menu_entry_new_full(char *label, Action *action,
125                                MenuEntryRenderType render_type,
126                                gpointer submenu)
127 {
128     MenuEntry *menu_entry = g_new(MenuEntry, 1);
129
130     menu_entry->label = g_strdup(label);
131     menu_entry->render_type = render_type;
132     menu_entry->action = action;
133
134     menu_entry->render_data = NULL;
135     menu_entry->submenu = submenu;
136
137     return menu_entry;
138 }
139
140 void menu_entry_set_submenu(MenuEntry *entry, Menu *submenu)
141 {
142     g_assert(entry != NULL);
143     
144     entry->submenu = submenu;
145
146     if(entry->parent != NULL)
147         entry->parent->invalid = TRUE;
148 }
149
150 void menu_add_entry(Menu *menu, MenuEntry *entry)
151 {
152     MenuEntryRenderData *data;
153     XSetWindowAttributes attrib;
154
155     g_assert(menu != NULL && entry != NULL && entry->render_data == NULL);
156
157     menu->entries = g_list_append(menu->entries, entry);
158     entry->parent = menu;
159
160     data = g_new(MenuEntryRenderData, 1);
161     data->item = createWindow(((MenuRenderData*)menu->render_data)->items,
162                               0, &attrib);
163     XMapWindow(ob_display, data->item);
164     data->a_item = appearance_copy(theme_a_menu_item);
165
166     entry->render_data = data;
167
168     menu->invalid = TRUE;
169 }
170
171 void menu_show(char *name, int x, int y, Client *client)
172 {
173     Menu *self;
174     MenuRenderData *data;
175     GList *it;
176     int w = 1;
177     int items_h;
178     int item_h = 0, nitems = 0; /* each item, only one is used */
179     int item_y;
180     int bullet_w;
181
182     self = g_hash_table_lookup(menu_hash, name);
183     if (!self) {
184         g_warning("Attempted to show menu '%s' but it does not exist.",
185                   name);
186         return;
187     }
188
189     data = self->render_data;
190
191     /* set texture data and size them mofos out */
192     data->a_title->texture[0].data.text.string = self->label;
193     appearance_minsize(data->a_title, &data->title_min_w, &data->title_h);
194     data->title_min_w += theme_bevel * 2;
195     data->title_h += theme_bevel * 2;
196     w = MAX(w, data->title_min_w);
197
198     for (it = self->entries; it; it = it->next) {
199         MenuEntryRenderData *r = ((MenuEntry*)it->data)->render_data;
200
201         r->a_item->texture[0].data.text.string = ((MenuEntry*)it->data)->label;
202         appearance_minsize(r->a_item, &r->min_w, &item_h);
203         r->min_w += theme_bevel * 2;
204         item_h += theme_bevel * 2;
205         w = MAX(w, r->min_w);
206         ++nitems;
207     }
208     bullet_w = item_h + theme_bevel;
209     w += 2 * bullet_w;
210     items_h = item_h * nitems;
211
212     /* size appearances */
213     RECT_SET(data->a_title->area, 0, 0, w, data->title_h);
214     RECT_SET(data->a_title->texture[0].position, 0, 0, w, data->title_h);
215     RECT_SET(data->a_items->area, 0, 0, w, items_h);
216     for (it = self->entries; it; it = it->next) {
217         MenuEntryRenderData *r = ((MenuEntry*)it->data)->render_data;
218         RECT_SET(r->a_item->area, 0, 0, w, item_h);
219         RECT_SET(r->a_item->texture[0].position, bullet_w, 0,
220                  w - 2 * bullet_w, item_h);
221     }
222
223     /* size windows and paint the suckers */
224     XMoveResizeWindow(ob_display, data->frame, x, y, w,
225                       data->title_h + items_h);
226     XMoveResizeWindow(ob_display, data->title, -theme_bwidth, -theme_bwidth,
227                       w, data->title_h);
228     paint(data->title, data->a_title);
229     XMoveResizeWindow(ob_display, data->items, 0, data->title_h + theme_bwidth,
230                       w, items_h);
231     paint(data->items, data->a_items);
232     for (item_y = 0, it = self->entries; it; item_y += item_h, it = it->next) {
233         MenuEntryRenderData *r = ((MenuEntry*)it->data)->render_data;
234         XMoveResizeWindow(ob_display, r->item, 0, item_y, w, item_h);
235         r->a_item->surface.data.planar.parent = data->a_items;
236         r->a_item->surface.data.planar.parentx = 0;
237         r->a_item->surface.data.planar.parenty = item_y;
238         paint(r->item, r->a_item);
239     }
240
241
242     XMapWindow(ob_display, data->frame);
243 }