Nothing to see here
[xiph/unicode.git] / sushivision / gtksucks.c
blob02b4dd0ccf1aec7eef33fe822ce1b001b005e945
1 /*
3 * sushivision copyright (C) 2006-2007 Monty <monty@xiph.org>
5 * sushivision is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * sushivision is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with sushivision; see the file COPYING. If not, write to the
17 * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
22 #define _GNU_SOURCE
23 #include <string.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <errno.h>
27 #include <gtk/gtk.h>
28 #include <pthread.h>
29 #include "sushivision.h"
30 #include "internal.h"
31 #include "gtksucks.h"
33 /* well, no, gtk doesn't actually suck, but it does have a number of
34 default behaviors that get in the way of building a UI the way
35 users have come to expect. This module encapsulates a number of
36 generic fixups that eliminate inconsistencies or obstructive
37 behaviors. */
39 /**********************************************************************/
40 /* Insensitive widgets (except children of viewports, a special case)
41 eat mouse events. This doesn't seem like a big deal, but if you're
42 implementing a right-click context menu, this means you cannot pop
43 a menu if the user was unlucky enough to have right-clicked over an
44 insensitive widget. Wrap the widget sensitivity setting method to
45 also also set/unset the GDK_BUTTON_PRESS_MASK on a widget; when
46 insensitive, this will remove its ability to silently swallow mouse
47 events. */
49 /* Note that this only works after the widget is realized, as
50 realization will clobber the event mask */
52 void _gtk_widget_set_sensitive_fixup(GtkWidget *w, gboolean state){
53 gdk_threads_enter();
54 gtk_widget_set_sensitive(w,state);
56 if(state)
57 gtk_widget_add_events (w, GDK_ALL_EVENTS_MASK);
58 else
59 _gtk_widget_remove_events (w, GDK_ALL_EVENTS_MASK);
60 gdk_threads_leave();
63 static void remove_events_internal (GtkWidget *widget,
64 gint events,
65 GList *window_list){
66 GList *l;
68 for (l = window_list; l != NULL; l = l->next){
69 GdkWindow *window = l->data;
70 gpointer user_data;
72 gdk_window_get_user_data (window, &user_data);
73 if (user_data == widget){
74 GList *children;
76 gdk_window_set_events (window, gdk_window_get_events(window) & (~events));
78 children = gdk_window_get_children (window);
79 remove_events_internal (widget, events, children);
80 g_list_free (children);
85 /* gtk provides a 'gtk_widget_add_events' but not a converse to remove
86 events. 'gtk_widget_set_events' only works pre-realization, so it
87 can't be used instead. */
88 void _gtk_widget_remove_events (GtkWidget *widget,
89 gint events){
91 g_return_if_fail (GTK_IS_WIDGET (widget));
93 GQuark quark_event_mask = g_quark_from_static_string ("gtk-event-mask");
94 gint *eventp = g_object_get_qdata (G_OBJECT (widget), quark_event_mask);
95 gint original_events = events;
97 if (!eventp){
98 eventp = g_slice_new (gint);
99 *eventp = 0;
102 events = ~events;
103 events &= *eventp;
105 if(events){
106 *eventp = events;
107 g_object_set_qdata (G_OBJECT (widget), quark_event_mask, eventp);
108 }else{
109 g_slice_free (gint, eventp);
110 g_object_set_qdata (G_OBJECT (widget), quark_event_mask, NULL);
113 if (GTK_WIDGET_REALIZED (widget)){
114 GList *window_list;
116 if (GTK_WIDGET_NO_WINDOW (widget))
117 window_list = gdk_window_get_children (widget->window);
118 else
119 window_list = g_list_prepend (NULL, widget->window);
121 remove_events_internal (widget, original_events, window_list);
123 g_list_free (window_list);
126 g_object_notify (G_OBJECT (widget), "events");
129 /**********************************************************************/
130 /* Buttons (and their subclasses) don't do anything with mousebutton3,
131 but they swallow the events anyway preventing them from cascading.
132 The 'supported' way of implementing a context-sensitive right-click
133 menu is to recursively bind a new handler to each and every button
134 on a toplevel. This is mad whack. The below 'fixes' buttons at
135 the class level by ramming a new button press handler into the
136 GtkButtonClass structure (and, unfortunately, button subclasses as
137 their classes have also already initialized and made a copy of the
138 Button's class structure and handlers */
140 static gboolean _gtk_button_button_press_new (GtkWidget *widget,
141 GdkEventButton *event){
143 if (event->type == GDK_BUTTON_PRESS){
144 GtkButton *button = GTK_BUTTON (widget);
146 if (event->button == 3)
147 return FALSE;
149 if (button->focus_on_click && !GTK_WIDGET_HAS_FOCUS (widget))
150 gtk_widget_grab_focus (widget);
152 if (event->button == 1)
153 gtk_button_pressed (button);
154 return TRUE;
157 return FALSE;
160 static gboolean _gtk_button_button_release_new (GtkWidget *widget,
161 GdkEventButton *event){
162 if (event->button == 1) {
163 GtkButton *button = GTK_BUTTON (widget);
164 gtk_button_released (button);
165 return TRUE;
168 return FALSE;
171 /* does not currently handle all button types, just the ones we use */
172 void _gtk_button3_fixup(){
174 GtkWidget *bb = gtk_button_new();
175 GtkWidgetClass *bc = GTK_WIDGET_GET_CLASS(bb);
176 bc->button_press_event = _gtk_button_button_press_new;
177 bc->button_release_event = _gtk_button_button_release_new;
179 bb = gtk_radio_button_new(NULL);
180 bc = GTK_WIDGET_GET_CLASS(bb);
181 bc->button_press_event = _gtk_button_button_press_new;
182 bc->button_release_event = _gtk_button_button_release_new;
184 bb = gtk_toggle_button_new();
185 bc = GTK_WIDGET_GET_CLASS(bb);
186 bc->button_press_event = _gtk_button_button_press_new;
187 bc->button_release_event = _gtk_button_button_release_new;
189 bb = gtk_check_button_new();
190 bc = GTK_WIDGET_GET_CLASS(bb);
191 bc->button_press_event = _gtk_button_button_press_new;
192 bc->button_release_event = _gtk_button_button_release_new;
194 // just leak 'em. they'll go away on exit.
198 /**********************************************************************/
199 /* fixup number 3: GDK uses whatever default mutex type offered by the
200 system, and this usually means non-recursive ('fast') mutextes.
201 The problem with this is that gdk_threads_enter() and
202 gdk_threads_leave() cannot be used in any call originating from the
203 main loop, but are required in calls from idle handlers and other
204 threads. In effect we would need seperate identical versions of
205 each widget method, one locked, one unlocked, depending on where
206 the call originated. Eliminate this problem by installing a
207 recursive mutex. */
209 static pthread_mutex_t gdkm;
210 static pthread_mutexattr_t gdkma;
211 static int depth = 0;
212 static int firstunder = 0;
214 void gdk_lock(void){
215 pthread_mutex_lock(&gdkm);
216 depth++;
219 void gdk_unlock(void){
220 depth--;
221 if(depth<0){
222 if(!firstunder){ // annoying detail of gtk locking; in apps that
223 // don't normally use threads, onr does not lock before entering
224 // mainloop; in apps that do thread, the mainloop must be
225 // locked. We can't tell which situation was in place before
226 // setting up our own threading, so allow one refcount error
227 // which we assume was the unlocked mainloop of a normally
228 // unthreaded gtk app.
229 firstunder++;
230 depth=0;
231 }else{
232 fprintf(stderr,"Internal locking error; refcount < 0. Dumping core for debugging\n");
233 abort();
235 }else
236 pthread_mutex_unlock(&gdkm);
239 void _gtk_mutex_fixup(){
240 pthread_mutexattr_init(&gdkma);
241 pthread_mutexattr_settype(&gdkma,PTHREAD_MUTEX_RECURSIVE);
242 pthread_mutex_init(&gdkm,&gdkma);
243 gdk_threads_set_lock_functions(gdk_lock,gdk_unlock);
246 /**********************************************************************/
247 /* Not really a fixup; generate menus that declare what the keyboard
248 shortcuts are */
250 static gint popup_callback (GtkWidget *widget, GdkEvent *event){
252 GtkMenu *menu = GTK_MENU (widget);
253 GdkEventButton *event_button = (GdkEventButton *) event;
255 if (event_button->button == 3){
256 gtk_menu_popup (menu, NULL, NULL, NULL, NULL,
257 event_button->button, event_button->time);
258 return TRUE;
261 return FALSE;
264 GtkWidget *_gtk_menu_new_twocol(GtkWidget *bind,
265 _sv_propmap_t **items,
266 void *callback_data){
268 _sv_propmap_t *ptr = *items++;
269 GtkWidget *ret = gtk_menu_new();
271 /* create packable boxes for labels, put left labels in */
272 while(ptr){
273 GtkWidget *item;
274 if(!strcmp(ptr->left,"")){
275 // seperator, not item
276 item = gtk_separator_menu_item_new();
277 gtk_menu_shell_append(GTK_MENU_SHELL(ret),item);
278 }else{
279 GtkWidget *box = gtk_hbox_new(0,10);
280 GtkWidget *left = gtk_label_new(NULL);
281 GtkWidget *right = NULL;
283 gtk_label_set_markup (GTK_LABEL (left), ptr->left);
285 item = gtk_menu_item_new();
286 gtk_container_add(GTK_CONTAINER(item),box);
287 gtk_box_pack_start(GTK_BOX(box),left,0,0,5);
289 if(ptr->right){
290 right = gtk_label_new(NULL);
291 gtk_label_set_markup (GTK_LABEL (right), ptr->right);
292 gtk_box_pack_end(GTK_BOX(box),right,0,0,5);
295 gtk_menu_shell_append(GTK_MENU_SHELL(ret),item);
296 if(ptr->callback)
297 g_signal_connect_swapped (G_OBJECT (item), "activate",
298 G_CALLBACK (ptr->callback), callback_data);
299 if(ptr->submenu){
300 GtkWidget *submenu = _gtk_menu_new_twocol(ret,ptr->submenu,callback_data);
301 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
304 gtk_widget_show_all(item);
306 ptr = *items++;
309 if(bind){
310 gtk_widget_add_events(bind, GDK_BUTTON_PRESS_MASK);
311 g_signal_connect_swapped (bind, "button-press-event",
312 G_CALLBACK (popup_callback), ret);
315 return ret;
318 GtkWidget *_gtk_menu_get_item(GtkMenu *m, int pos){
319 int i=0;
320 GList *l=gtk_container_get_children (GTK_CONTAINER(m));
322 while(l){
323 if(i == pos){
324 GtkWidget *ret = (GtkWidget *)l->data;
325 g_list_free (l);
326 return ret;
329 i++;
330 l=l->next;
333 return NULL;
336 int _gtk_menu_item_position(GtkWidget *w){
337 //GtkMenuItem *mi = GTK_MENU_ITEM(w);
338 GtkWidget *box = gtk_widget_get_parent(w);
339 GList *l = gtk_container_get_children(GTK_CONTAINER(box));
340 int i = 0;
342 while(l){
343 if((GtkWidget *)l->data == w){
344 g_list_free (l);
345 return i;
347 i++;
348 l = l->next;
351 return 0;
354 void _gtk_menu_alter_item_label(GtkMenu *m, int pos, char *text){
355 GList *l;
356 GtkWidget *box=NULL;
357 GtkWidget *label=NULL;
358 GtkWidget *item = _gtk_menu_get_item(m, pos);
359 if(!item)return;
361 l=gtk_container_get_children (GTK_CONTAINER(item));
362 box = l->data;
363 g_list_free(l);
365 if(!box)return;
367 l=gtk_container_get_children (GTK_CONTAINER(box));
368 label = l->data;
369 g_list_free(l);
371 if(!label)return;
373 gtk_label_set_markup(GTK_LABEL(label),text);
376 void _gtk_menu_alter_item_right(GtkMenu *m, int pos, char *text){
377 GList *l;
378 GtkWidget *box=NULL;
379 GtkWidget *label=NULL;
380 GtkWidget *item = _gtk_menu_get_item(m, pos);
381 if(!item)return;
383 l=gtk_container_get_children (GTK_CONTAINER(item));
384 box = l->data;
385 g_list_free(l);
387 if(!box)return;
389 l=gtk_container_get_children (GTK_CONTAINER(box));
390 if(l && l->next)
391 label = l->next->data;
392 g_list_free(l);
394 if(!label)return;
396 gtk_label_set_markup(GTK_LABEL(label),text);
399 /**********************************************************************/
400 /* unlock text combo boxes to support markup as well as straight text */
402 GtkWidget *_gtk_combo_box_new_markup (void){
403 GtkWidget *combo_box;
404 GtkCellRenderer *cell;
405 GtkListStore *store;
407 store = gtk_list_store_new (1, G_TYPE_STRING);
408 combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
409 g_object_unref (store);
411 cell = gtk_cell_renderer_text_new ();
412 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE);
413 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
414 "markup", 0,
415 NULL);
417 return combo_box;
420 /**********************************************************************/
421 /* Gtk has an annoying habit of 'overresizing'; superfluous resize
422 events within panels that cause all hell to break loose for a
423 while, only to have the panel finally look exactly like it did when
424 it started. This happens especially with expander widgets. There
425 are two problems: One, it's ugly as Hell to click an expander and
426 have the whole window freak out just to make a little space.
427 Second, having the Plot resize will trigger a recompute, which
428 could seriously screw the user.
430 The below lets us freeze/unfreeze an auto-resizing box's
431 child at its current size without queueing resize events. */
433 void _gtk_box_freeze_child (GtkBox *box,
434 GtkWidget *child){
435 GList *list;
436 GtkBoxChild *child_info = NULL;
438 g_return_if_fail (GTK_IS_BOX (box));
439 g_return_if_fail (GTK_IS_WIDGET (child));
441 list = box->children;
442 while (list){
443 child_info = list->data;
444 if (child_info->widget == child)
445 break;
447 list = list->next;
450 if (list){
451 child_info->expand = FALSE;
452 child_info->fill = FALSE;
456 void _gtk_box_unfreeze_child (GtkBox *box,
457 GtkWidget *child){
458 GList *list;
459 GtkBoxChild *child_info = NULL;
461 g_return_if_fail (GTK_IS_BOX (box));
462 g_return_if_fail (GTK_IS_WIDGET (child));
464 list = box->children;
465 while (list){
466 child_info = list->data;
467 if (child_info->widget == child)
468 break;
470 list = list->next;
473 if (list){
474 child_info->expand = TRUE;
475 child_info->fill = TRUE;