Implement AnjutaSyncCommand class in libanjuta.
[anjuta-git-plugin.git] / libegg / eggcomboselect.c
blob927a587dac9ab03a71c40095824e58e4c18d3019
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
3 /* eggcomboselect widget
5 * Copyright (C) Naba Kumar <naba@gnome.org>
7 * Codes taken from:
8 * gtkcombo_select - combo_select widget for gtk+
9 * Copyright 1999-2001 Adrian E. Feiguin <feiguin@ifir.edu.ar>
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Library General Public
13 * License as published by the Free Software Foundation; either
14 * version 2 of the License, or (at your option) any later version.
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Library General Public License for more details.
21 * You should have received a copy of the GNU Library General Public
22 * License along with this library; if not, write to the
23 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 * Boston, MA 02110-1301, USA.
27 #include <string.h>
28 #include <gtk/gtkhbox.h>
29 #include <gtk/gtktogglebutton.h>
30 #include <gtk/gtkarrow.h>
31 #include <gtk/gtkeventbox.h>
32 #include <gtk/gtkmain.h>
33 #include <gtk/gtksignal.h>
34 #include <gtk/gtkwindow.h>
35 #include <gdk/gdkkeysyms.h>
36 #include <gtk/gtkframe.h>
37 #include <gtk/gtkscrolledwindow.h>
38 #include <gtk/gtktreeview.h>
39 #include <gtk/gtkcelllayout.h>
40 #include "gtkcellview.h"
42 #include <libegg/menu/eggcomboselect.h>
44 static void egg_combo_select_class_init (EggComboSelectClass *klass);
45 static void egg_combo_select_init (EggComboSelect *combo_select);
46 static void egg_combo_select_dispose (GObject *combo_select);
47 static void egg_combo_select_finalize (GObject *combo_select);
48 static void egg_combo_select_get_pos (EggComboSelect *combo_select,
49 gint *x,
50 gint *y,
51 gint *height,
52 gint *width);
53 static gint egg_combo_select_button_toggled (GtkWidget * widget,
54 EggComboSelect * combo_select);
55 static void egg_combo_select_arrow_clicked (GtkWidget * widget,
56 EggComboSelect * combo_select);
57 static gint egg_combo_select_button_press (GtkWidget *widget,
58 GdkEvent *event,
59 gpointer data);
60 static gboolean egg_combo_select_key_press (GtkWidget * widget, GdkEventKey * event, gpointer data);
61 static void egg_combo_select_size_allocate (GtkWidget *widget,
62 GtkAllocation *allocation);
63 static void egg_combo_select_size_request (GtkWidget *widget,
64 GtkRequisition *requisition);
65 static void egg_combo_select_sync_cells (EggComboSelect *combo_select,
66 GtkCellLayout *cell_layout);
68 struct _EggComboSelectPriv {
69 GtkWidget *arrow;
70 GtkWidget *popup;
71 GtkWidget *popwin;
72 GtkWidget *button;
73 GtkWidget *frame;
74 GtkWidget *treeview;
75 GtkTreeModel *model;
76 GtkTreeViewColumn *column;
77 GSList *cells;
78 GtkWidget *cell_view;
79 GtkWidget *cell_frame;
80 GtkTreeRowReference *active_row;
81 gchar *title;
84 typedef struct _ComboSelectCellInfo ComboSelectCellInfo;
85 struct _ComboSelectCellInfo
87 GtkCellRenderer *cell;
88 GSList *attributes;
90 GtkCellLayoutDataFunc func;
91 gpointer func_data;
92 GDestroyNotify destroy;
94 guint expand : 1;
95 guint pack : 1;
98 enum {
99 CHANGED,
100 LAST_SIGNAL
103 static GtkHBoxClass *parent_class = NULL;
104 static guint action_signals[LAST_SIGNAL] = { 0 };
106 /* Celllayout interface implementation */
108 static ComboSelectCellInfo *
109 cell_get_info (EggComboSelect *combo_select,
110 GtkCellRenderer *cell)
112 GSList *i;
114 for (i = combo_select->priv->cells; i; i = i->next)
116 ComboSelectCellInfo *info = (ComboSelectCellInfo *)i->data;
118 if (info && info->cell == cell)
119 return info;
122 return NULL;
125 static void
126 cell_layout_pack_start (GtkCellLayout *layout,
127 GtkCellRenderer *cell,
128 gboolean expand)
130 ComboSelectCellInfo *info;
131 EggComboSelect *combo_select;
133 g_return_if_fail (EGG_IS_COMBO_SELECT (layout));
134 g_return_if_fail (GTK_IS_CELL_RENDERER (cell));
136 combo_select = EGG_COMBO_SELECT (layout);
138 g_object_ref (cell);
139 gtk_object_sink (GTK_OBJECT (cell));
141 info = g_new0 (ComboSelectCellInfo, 1);
142 info->cell = cell;
143 info->expand = expand;
144 info->pack = GTK_PACK_START;
146 combo_select->priv->cells = g_slist_append (combo_select->priv->cells, info);
148 if (combo_select->priv->cell_view)
149 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_select->priv->cell_view),
150 cell, expand);
152 if (combo_select->priv->column)
153 gtk_tree_view_column_pack_start (combo_select->priv->column, cell, expand);
156 static void
157 cell_layout_pack_end (GtkCellLayout *layout,
158 GtkCellRenderer *cell,
159 gboolean expand)
161 ComboSelectCellInfo *info;
162 EggComboSelect *combo_select;
164 g_return_if_fail (EGG_IS_COMBO_SELECT (layout));
165 g_return_if_fail (GTK_IS_CELL_RENDERER (cell));
167 combo_select = EGG_COMBO_SELECT (layout);
169 g_object_ref (cell);
170 gtk_object_sink (GTK_OBJECT (cell));
172 info = g_new0 (ComboSelectCellInfo, 1);
173 info->cell = cell;
174 info->expand = expand;
175 info->pack = GTK_PACK_END;
177 combo_select->priv->cells = g_slist_append (combo_select->priv->cells, info);
179 if (combo_select->priv->cell_view)
180 gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (combo_select->priv->cell_view),
181 cell, expand);
183 if (combo_select->priv->column)
184 gtk_tree_view_column_pack_end (combo_select->priv->column, cell, expand);
187 static void
188 cell_layout_clear_attributes (GtkCellLayout *layout,
189 GtkCellRenderer *cell)
191 ComboSelectCellInfo *info;
192 EggComboSelect *combo_select;
193 GSList *list;
195 g_return_if_fail (EGG_IS_COMBO_SELECT (layout));
196 g_return_if_fail (GTK_IS_CELL_RENDERER (cell));
198 combo_select = EGG_COMBO_SELECT (layout);
200 info = cell_get_info (combo_select, cell);
201 g_return_if_fail (info != NULL);
203 list = info->attributes;
204 while (list && list->next)
206 g_free (list->data);
207 list = list->next->next;
209 g_slist_free (info->attributes);
210 info->attributes = NULL;
212 if (combo_select->priv->cell_view)
213 gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (combo_select->priv->cell_view), cell);
215 if (combo_select->priv->column)
216 gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (combo_select->priv->column), cell);
218 gtk_widget_queue_resize (GTK_WIDGET (combo_select));
221 static void
222 cell_layout_clear (GtkCellLayout *layout)
224 EggComboSelect *combo_select;
225 GSList *i;
227 g_return_if_fail (EGG_IS_COMBO_SELECT (layout));
229 combo_select = EGG_COMBO_SELECT (layout);
231 if (combo_select->priv->cell_view)
232 gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_select->priv->cell_view));
234 if (combo_select->priv->column)
235 gtk_tree_view_column_clear (combo_select->priv->column);
237 for (i = combo_select->priv->cells; i; i = i->next)
239 ComboSelectCellInfo *info = (ComboSelectCellInfo *)i->data;
241 cell_layout_clear_attributes (layout, info->cell);
242 g_object_unref (info->cell);
243 g_free (info);
244 i->data = NULL;
246 g_slist_free (combo_select->priv->cells);
247 combo_select->priv->cells = NULL;
250 static void
251 cell_layout_add_attribute (GtkCellLayout *layout,
252 GtkCellRenderer *cell,
253 const gchar *attribute,
254 gint column)
256 ComboSelectCellInfo *info;
257 EggComboSelect *combo_select;
259 g_return_if_fail (EGG_IS_COMBO_SELECT (layout));
260 g_return_if_fail (GTK_IS_CELL_RENDERER (cell));
262 combo_select = EGG_COMBO_SELECT (layout);
264 info = cell_get_info (combo_select, cell);
266 info->attributes = g_slist_prepend (info->attributes,
267 GINT_TO_POINTER (column));
268 info->attributes = g_slist_prepend (info->attributes,
269 g_strdup (attribute));
271 if (combo_select->priv->cell_view)
272 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_select->priv->cell_view),
273 cell, attribute, column);
275 if (combo_select->priv->column)
276 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_select->priv->column),
277 cell, attribute, column);
278 gtk_widget_queue_resize (GTK_WIDGET (combo_select));
281 static void
282 combo_cell_data_func (GtkCellLayout *cell_layout,
283 GtkCellRenderer *cell,
284 GtkTreeModel *tree_model,
285 GtkTreeIter *iter,
286 gpointer data)
288 ComboSelectCellInfo *info = (ComboSelectCellInfo *)data;
289 GtkWidget *parent = NULL;
291 if (!info->func)
292 return;
294 (*info->func) (cell_layout, cell, tree_model, iter, info->func_data);
297 static void
298 cell_layout_set_cell_data_func (GtkCellLayout *layout,
299 GtkCellRenderer *cell,
300 GtkCellLayoutDataFunc func,
301 gpointer func_data,
302 GDestroyNotify destroy)
304 ComboSelectCellInfo *info;
305 EggComboSelect *combo_select;
307 g_return_if_fail (EGG_IS_COMBO_SELECT (layout));
309 combo_select = EGG_COMBO_SELECT (layout);
311 info = cell_get_info (combo_select, cell);
312 g_return_if_fail (info != NULL);
314 if (info->destroy)
316 GDestroyNotify d = info->destroy;
318 info->destroy = NULL;
319 d (info->func_data);
322 info->func = func;
323 info->func_data = func_data;
324 info->destroy = destroy;
326 if (combo_select->priv->cell_view)
327 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo_select->priv->cell_view), cell, func, func_data, NULL);
329 if (combo_select->priv->column)
330 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo_select->priv->column), cell, func, func_data, NULL);
332 gtk_widget_queue_resize (GTK_WIDGET (combo_select));
335 static void
336 cell_layout_reorder (GtkCellLayout *layout,
337 GtkCellRenderer *cell,
338 gint position)
340 ComboSelectCellInfo *info;
341 EggComboSelect *combo_select;
342 GSList *link;
344 g_return_if_fail (EGG_IS_COMBO_SELECT (layout));
345 g_return_if_fail (GTK_IS_CELL_RENDERER (cell));
347 combo_select = EGG_COMBO_SELECT (layout);
349 info = cell_get_info (combo_select, cell);
351 g_return_if_fail (info != NULL);
352 g_return_if_fail (position >= 0);
354 link = g_slist_find (combo_select->priv->cells, info);
356 g_return_if_fail (link != NULL);
358 combo_select->priv->cells = g_slist_remove_link (combo_select->priv->cells, link);
359 combo_select->priv->cells = g_slist_insert (combo_select->priv->cells, info,
360 position);
362 if (combo_select->priv->cell_view)
363 gtk_cell_layout_reorder (GTK_CELL_LAYOUT (combo_select->priv->cell_view),
364 cell, position);
366 if (combo_select->priv->column)
367 gtk_cell_layout_reorder (GTK_CELL_LAYOUT (combo_select->priv->column),
368 cell, position);
370 gtk_widget_queue_draw (GTK_WIDGET (combo_select));
373 static void
374 cell_layout_init (GtkCellLayoutIface *iface)
376 iface->pack_start = cell_layout_pack_start;
377 iface->pack_end = cell_layout_pack_end;
378 iface->reorder = cell_layout_reorder;
379 iface->clear = cell_layout_clear;
380 iface->add_attribute = cell_layout_add_attribute;
381 iface->set_cell_data_func = cell_layout_set_cell_data_func;
382 iface->clear_attributes = cell_layout_clear_attributes;
385 /* EggComboSelect class */
386 static void
387 egg_combo_select_class_init (EggComboSelectClass * klass)
389 GObjectClass *object_class;
390 GtkWidgetClass *widget_class;
392 parent_class = g_type_class_peek_parent (klass);
393 object_class = (GObjectClass *) klass;
394 widget_class = (GtkWidgetClass *) klass;
396 object_class->dispose = egg_combo_select_dispose;
397 object_class->finalize = egg_combo_select_finalize;
399 widget_class->size_allocate = egg_combo_select_size_allocate;
400 widget_class->size_request = egg_combo_select_size_request;
402 action_signals[CHANGED] =
403 g_signal_new ("changed",
404 G_OBJECT_CLASS_TYPE (klass),
405 G_SIGNAL_RUN_FIRST,
406 G_STRUCT_OFFSET (EggComboSelectClass, changed),
407 NULL, NULL,
408 g_cclosure_marshal_VOID__VOID,
409 G_TYPE_NONE, 0);
412 static void
413 egg_combo_select_dispose (GObject * combo_select)
415 if (EGG_COMBO_SELECT (combo_select)->priv->popwin)
417 egg_combo_select_popdown (EGG_COMBO_SELECT (combo_select));
419 if (EGG_COMBO_SELECT (combo_select)->priv->model)
421 g_object_unref (EGG_COMBO_SELECT (combo_select)->priv->model);
422 EGG_COMBO_SELECT (combo_select)->priv->model = NULL;
424 if (G_OBJECT_CLASS (parent_class)->dispose)
425 (*G_OBJECT_CLASS (parent_class)->dispose) (combo_select);
428 static void
429 egg_combo_select_finalize (GObject * combo_select)
431 g_free (EGG_COMBO_SELECT(combo_select)->priv->title);
432 g_free (EGG_COMBO_SELECT(combo_select)->priv);
433 if (G_OBJECT_CLASS (parent_class)->finalize)
434 (*G_OBJECT_CLASS (parent_class)->finalize) (combo_select);
437 /***************************************************************/
439 static void
440 egg_combo_select_get_position (EggComboSelect *combo_select,
441 gint *x,
442 gint *y,
443 gint *width,
444 gint *height)
446 GtkWidget *sample;
447 GdkScreen *screen;
448 gint monitor_num;
449 GdkRectangle monitor;
450 GtkRequisition popup_req;
451 GtkPolicyType hpolicy, vpolicy;
453 // sample = GTK_BIN (combo_select->priv->button)->child;
454 sample = combo_select->priv->button;
456 gdk_window_get_origin (sample->window, x, y);
458 if (GTK_WIDGET_NO_WINDOW (sample))
460 *x += sample->allocation.x;
461 *y += sample->allocation.y;
464 *width = sample->allocation.width;
466 /* *x -= GTK_CONTAINER (combo_select->priv->button)->border_width +
467 GTK_WIDGET (combo_select->priv->button)->style->xthickness;
468 *width += 2 * (GTK_CONTAINER (combo_select->priv->button)->border_width +
469 GTK_WIDGET (combo_select->priv->button)->style->xthickness);
471 hpolicy = vpolicy = GTK_POLICY_NEVER;
472 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (combo_select->priv->frame),
473 hpolicy, vpolicy);
474 gtk_widget_size_request (combo_select->priv->frame, &popup_req);
476 if (popup_req.width > *width)
478 hpolicy = GTK_POLICY_ALWAYS;
479 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (combo_select->priv->frame),
480 hpolicy, vpolicy);
481 gtk_widget_size_request (combo_select->priv->frame, &popup_req);
483 *width = popup_req.width;
484 *height = popup_req.height;
486 screen = gtk_widget_get_screen (GTK_WIDGET (combo_select));
487 monitor_num = gdk_screen_get_monitor_at_window (screen,
488 GTK_WIDGET (combo_select)->window);
489 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
491 if (*x < monitor.x)
492 *x = monitor.x;
493 else if (*x + *width > monitor.x + monitor.width)
494 *x = monitor.x + monitor.width - *width;
496 if (*y + sample->allocation.height + *height <= monitor.y + monitor.height)
497 *y += sample->allocation.height;
498 else if (*y - *height >= monitor.y)
499 *y -= *height;
500 else if (monitor.y + monitor.height - (*y + sample->allocation.height) > *y - monitor.y)
502 *y += sample->allocation.height;
503 *height = monitor.y + monitor.height - *y;
505 else
507 *height = *y - monitor.y;
508 *y = monitor.y;
511 if (popup_req.height > *height)
513 vpolicy = GTK_POLICY_ALWAYS;
515 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (combo_select->priv->frame),
516 hpolicy, vpolicy);
520 static gboolean
521 list_popup_resize_idle (gpointer user_data)
523 EggComboSelect *combo_select;
524 gint x, y, width, height;
526 GDK_THREADS_ENTER ();
528 combo_select = EGG_COMBO_SELECT (user_data);
530 if (combo_select->priv->treeview &&
531 GTK_WIDGET_MAPPED (combo_select->priv->popwin))
533 egg_combo_select_get_position (combo_select, &x, &y, &width, &height);
535 gtk_widget_set_size_request (combo_select->priv->popwin, width, height);
536 gtk_window_move (GTK_WINDOW (combo_select->priv->popwin), x, y);
539 GDK_THREADS_LEAVE ();
541 return FALSE;
544 static void
545 egg_combo_select_popup_resize (EggComboSelect *combo_select)
547 g_idle_add (list_popup_resize_idle, combo_select);
550 static void
551 on_treeview_selection_changed (GtkTreeSelection *selection,
552 EggComboSelect *combo_select)
554 GtkTreeIter iter;
555 GtkTreeModel *model;
557 if (gtk_tree_selection_get_selected (selection, &model, &iter))
559 egg_combo_select_set_active_iter (combo_select, &iter);
561 egg_combo_select_popdown (combo_select);
564 void
565 egg_combo_select_popup (EggComboSelect * combo_select)
567 gint height, width, x, y;
568 gint old_width, old_height;
569 GtkWidget *event_box;
570 GdkCursor *cursor;
571 GtkTreeSelection *selection;
572 GtkTreeIter iter;
574 combo_select->priv->popwin = gtk_window_new (GTK_WINDOW_POPUP);
576 gtk_widget_ref (combo_select->priv->popwin);
577 gtk_window_set_policy (GTK_WINDOW (combo_select->priv->popwin), 1, 1, 0);
578 gtk_widget_set_events (combo_select->priv->popwin, GDK_KEY_PRESS_MASK);
579 g_signal_connect (G_OBJECT (combo_select->priv->popwin), "button_press_event",
580 G_CALLBACK (egg_combo_select_button_press), combo_select);
581 g_signal_connect (G_OBJECT (combo_select->priv->popwin), "key_press_event",
582 G_CALLBACK (egg_combo_select_key_press), combo_select);
584 event_box = gtk_event_box_new ();
585 gtk_container_add (GTK_CONTAINER (combo_select->priv->popwin), event_box);
586 gtk_widget_show (event_box);
588 gtk_widget_realize (event_box);
589 cursor = gdk_cursor_new (GDK_TOP_LEFT_ARROW);
590 gdk_window_set_cursor (event_box->window, cursor);
591 gdk_cursor_unref (cursor);
593 combo_select->priv->frame = gtk_scrolled_window_new (NULL, NULL);
594 gtk_container_add (GTK_CONTAINER (event_box), combo_select->priv->frame);
595 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (combo_select->priv->frame),
596 GTK_SHADOW_OUT);
597 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (combo_select->priv->frame),
598 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
599 gtk_widget_show (combo_select->priv->frame);
601 combo_select->priv->treeview = gtk_tree_view_new ();
602 if (combo_select->priv->model)
603 gtk_tree_view_set_model (GTK_TREE_VIEW (combo_select->priv->treeview),
604 combo_select->priv->model);
606 gtk_widget_show (combo_select->priv->treeview);
607 gtk_container_add (GTK_CONTAINER (combo_select->priv->frame), combo_select->priv->treeview);
609 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (combo_select->priv->treeview));
611 combo_select->priv->column = gtk_tree_view_column_new ();
612 if (combo_select->priv->title)
613 gtk_tree_view_column_set_title (combo_select->priv->column,
614 combo_select->priv->title);
615 g_object_ref (combo_select->priv->column);
616 egg_combo_select_sync_cells (combo_select,
617 GTK_CELL_LAYOUT (combo_select->priv->column));
619 gtk_tree_view_append_column (GTK_TREE_VIEW (combo_select->priv->treeview),
620 combo_select->priv->column);
622 old_width = combo_select->priv->popwin->allocation.width;
623 old_height = combo_select->priv->popwin->allocation.height;
625 egg_combo_select_get_position (combo_select, &x, &y, &width, &height);
626 gtk_widget_set_size_request (combo_select->priv->popwin, width, height);
627 gtk_window_move (GTK_WINDOW (combo_select->priv->popwin), x, y);
628 gtk_widget_show (combo_select->priv->popwin);
630 gtk_grab_add (combo_select->priv->popwin);
631 gdk_pointer_grab (combo_select->priv->popwin->window, TRUE,
632 GDK_BUTTON_PRESS_MASK |
633 GDK_BUTTON_RELEASE_MASK |
634 GDK_POINTER_MOTION_MASK,
635 NULL, NULL, GDK_CURRENT_TIME);
636 if (egg_combo_select_get_active_iter (combo_select, &iter))
637 gtk_tree_selection_select_iter (selection, &iter);
638 g_signal_connect (G_OBJECT (selection), "changed",
639 G_CALLBACK (on_treeview_selection_changed), combo_select);
642 static gboolean
643 destroy_widget_on_idle (gpointer data)
645 gtk_widget_destroy (GTK_WIDGET (data));
646 return FALSE;
649 void
650 egg_combo_select_popdown (EggComboSelect *combo_select)
652 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(combo_select->priv->button), FALSE);
654 gtk_grab_remove(combo_select->priv->popwin);
655 gdk_pointer_ungrab(GDK_CURRENT_TIME);
656 gtk_widget_hide(combo_select->priv->popwin);
658 g_object_unref (combo_select->priv->column);
659 g_idle_add (destroy_widget_on_idle, combo_select->priv->popwin);
661 combo_select->priv->popwin = NULL;
662 combo_select->priv->treeview = NULL;
663 combo_select->priv->column = NULL;
664 combo_select->priv->frame = NULL;
667 static gint
668 egg_combo_select_button_toggled (GtkWidget * widget, EggComboSelect * combo_select)
670 GtkToggleButton *button;
672 button = GTK_TOGGLE_BUTTON(widget);
674 if(!button->active){
675 gtk_widget_hide (combo_select->priv->popwin);
676 gtk_grab_remove (combo_select->priv->popwin);
677 gdk_pointer_ungrab (GDK_CURRENT_TIME);
678 return TRUE;
681 egg_combo_select_popup (combo_select);
682 return TRUE;
685 static void
686 egg_combo_select_arrow_clicked (GtkWidget *widget, EggComboSelect * combo_select)
688 g_signal_emit_by_name (G_OBJECT (combo_select), "changed");
691 static void
692 egg_combo_select_sync_cells (EggComboSelect *combo_select,
693 GtkCellLayout *cell_layout)
695 GSList *k;
697 for (k = combo_select->priv->cells; k; k = k->next)
699 GSList *j;
700 ComboSelectCellInfo *info = (ComboSelectCellInfo *)k->data;
702 if (info->pack == GTK_PACK_START)
703 gtk_cell_layout_pack_start (cell_layout,
704 info->cell, info->expand);
705 else if (info->pack == GTK_PACK_END)
706 gtk_cell_layout_pack_end (cell_layout,
707 info->cell, info->expand);
709 gtk_cell_layout_set_cell_data_func (cell_layout,
710 info->cell,
711 combo_cell_data_func, info, NULL);
713 for (j = info->attributes; j; j = j->next->next)
715 gtk_cell_layout_add_attribute (cell_layout,
716 info->cell,
717 j->data,
718 GPOINTER_TO_INT (j->next->data));
723 static void
724 egg_combo_select_init (EggComboSelect * combo_select)
726 GtkWidget *widget;
727 GtkWidget *arrow;
729 widget=GTK_WIDGET(combo_select);
731 combo_select->priv = g_new0 (EggComboSelectPriv, 1);
732 combo_select->priv->model = NULL;
733 combo_select->priv->popwin = NULL;
734 combo_select->priv->frame = NULL;
735 combo_select->priv->column = NULL;
736 combo_select->priv->active_row = NULL;
737 combo_select->priv->title = NULL;
739 gtk_box_set_spacing (GTK_BOX (widget), 5);
740 gtk_box_set_homogeneous (GTK_BOX (widget), FALSE);
742 /* Create button and cell view */
743 combo_select->priv->button = gtk_toggle_button_new ();
744 gtk_widget_show (combo_select->priv->button);
745 gtk_box_pack_start (GTK_BOX (combo_select), combo_select->priv->button, FALSE, FALSE, 0);
747 combo_select->priv->cell_view = gtk_cell_view_new ();
748 gtk_widget_show (combo_select->priv->cell_view);
749 gtk_container_add (GTK_CONTAINER (combo_select->priv->button),
750 combo_select->priv->cell_view);
752 /* Create arrow widget */
753 combo_select->priv->arrow = gtk_button_new ();
754 gtk_widget_show (combo_select->priv->arrow);
755 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
756 gtk_widget_show (arrow);
757 gtk_container_add (GTK_CONTAINER (combo_select->priv->arrow), arrow);
758 gtk_box_pack_start (GTK_BOX (combo_select), combo_select->priv->arrow, FALSE, FALSE, 0);
760 g_signal_connect (G_OBJECT (combo_select->priv->button), "toggled",
761 G_CALLBACK (egg_combo_select_button_toggled), combo_select);
762 g_signal_connect (G_OBJECT (combo_select->priv->arrow), "clicked",
763 G_CALLBACK (egg_combo_select_arrow_clicked), combo_select);
766 GType
767 egg_combo_select_get_type ()
769 static GType combo_select_type = 0;
771 if (!combo_select_type)
773 static const GTypeInfo combo_select_info =
775 sizeof (EggComboSelectClass),
776 NULL, /* base_init */
777 NULL, /* base_finalize */
778 (GClassInitFunc) egg_combo_select_class_init,
779 NULL, /* class_finalize */
780 NULL, /* class_data */
781 sizeof (EggComboSelect),
783 (GInstanceInitFunc) egg_combo_select_init
785 static const GInterfaceInfo cell_layout_info =
787 (GInterfaceInitFunc) cell_layout_init,
788 NULL,
789 NULL
791 combo_select_type = g_type_register_static (GTK_TYPE_HBOX,
792 "EggComboSelect",
793 &combo_select_info,
795 g_type_add_interface_static (combo_select_type,
796 GTK_TYPE_CELL_LAYOUT,
797 &cell_layout_info);
799 return combo_select_type;
802 GtkWidget *
803 egg_combo_select_new ()
805 EggComboSelect *combo_select;
807 combo_select = gtk_type_new (egg_combo_select_get_type ());
809 return(GTK_WIDGET(combo_select));
813 typedef struct {
814 EggComboSelect *combo;
815 GtkTreePath *path;
816 GtkTreeIter iter;
817 gboolean found;
818 gboolean set;
819 gboolean visible;
820 } SearchData;
822 static gboolean
823 path_visible (GtkTreeView *view,
824 GtkTreePath *path)
826 /* Note that we assume all paths visible. FIXME.
828 return TRUE;
831 static gboolean
832 tree_column_row_is_sensitive (EggComboSelect *combo_select,
833 GtkTreeIter *iter)
835 return TRUE;
838 static gboolean
839 tree_next_func (GtkTreeModel *model,
840 GtkTreePath *path,
841 GtkTreeIter *iter,
842 gpointer data)
844 SearchData *search_data = (SearchData *)data;
846 if (search_data->found)
848 if (!tree_column_row_is_sensitive (search_data->combo, iter))
849 return FALSE;
851 if (search_data->visible &&
852 !path_visible (GTK_TREE_VIEW (search_data->combo->priv->treeview), path))
853 return FALSE;
855 search_data->set = TRUE;
856 search_data->iter = *iter;
858 return TRUE;
861 if (gtk_tree_path_compare (path, search_data->path) == 0)
862 search_data->found = TRUE;
864 return FALSE;
867 static gboolean
868 tree_next (EggComboSelect *combo,
869 GtkTreeModel *model,
870 GtkTreeIter *iter,
871 GtkTreeIter *next,
872 gboolean visible)
874 SearchData search_data;
876 search_data.combo = combo;
877 search_data.path = gtk_tree_model_get_path (model, iter);
878 search_data.visible = visible;
879 search_data.found = FALSE;
880 search_data.set = FALSE;
882 gtk_tree_model_foreach (model, tree_next_func, &search_data);
884 *next = search_data.iter;
886 gtk_tree_path_free (search_data.path);
888 return search_data.set;
891 static gboolean
892 tree_prev_func (GtkTreeModel *model,
893 GtkTreePath *path,
894 GtkTreeIter *iter,
895 gpointer data)
897 SearchData *search_data = (SearchData *)data;
899 if (gtk_tree_path_compare (path, search_data->path) == 0)
901 search_data->found = TRUE;
902 return TRUE;
905 if (!tree_column_row_is_sensitive (search_data->combo, iter))
906 return FALSE;
908 if (search_data->visible &&
909 !path_visible (GTK_TREE_VIEW (search_data->combo->priv->treeview), path))
910 return FALSE;
912 search_data->set = TRUE;
913 search_data->iter = *iter;
915 return FALSE;
918 static gboolean
919 tree_prev (EggComboSelect *combo,
920 GtkTreeModel *model,
921 GtkTreeIter *iter,
922 GtkTreeIter *prev,
923 gboolean visible)
925 SearchData search_data;
927 search_data.combo = combo;
928 search_data.path = gtk_tree_model_get_path (model, iter);
929 search_data.visible = visible;
930 search_data.found = FALSE;
931 search_data.set = FALSE;
933 gtk_tree_model_foreach (model, tree_prev_func, &search_data);
935 *prev = search_data.iter;
937 gtk_tree_path_free (search_data.path);
939 return search_data.set;
942 static gboolean
943 tree_last_func (GtkTreeModel *model,
944 GtkTreePath *path,
945 GtkTreeIter *iter,
946 gpointer data)
948 SearchData *search_data = (SearchData *)data;
950 if (!tree_column_row_is_sensitive (search_data->combo, iter))
951 return FALSE;
953 /* Note that we rely on the fact that collapsed rows don't have nodes
955 if (search_data->visible &&
956 !path_visible (GTK_TREE_VIEW (search_data->combo->priv->treeview), path))
957 return FALSE;
959 search_data->set = TRUE;
960 search_data->iter = *iter;
962 return FALSE;
965 static gboolean
966 tree_last (EggComboSelect *combo,
967 GtkTreeModel *model,
968 GtkTreeIter *last,
969 gboolean visible)
971 SearchData search_data;
973 search_data.combo = combo;
974 search_data.visible = visible;
975 search_data.set = FALSE;
977 gtk_tree_model_foreach (model, tree_last_func, &search_data);
979 *last = search_data.iter;
981 return search_data.set;
985 static gboolean
986 tree_first_func (GtkTreeModel *model,
987 GtkTreePath *path,
988 GtkTreeIter *iter,
989 gpointer data)
991 SearchData *search_data = (SearchData *)data;
993 if (!tree_column_row_is_sensitive (search_data->combo, iter))
994 return FALSE;
996 if (search_data->visible &&
997 !path_visible (GTK_TREE_VIEW (search_data->combo->priv->treeview), path))
998 return FALSE;
1000 search_data->set = TRUE;
1001 search_data->iter = *iter;
1003 return TRUE;
1006 static gboolean
1007 tree_first (EggComboSelect *combo,
1008 GtkTreeModel *model,
1009 GtkTreeIter *first,
1010 gboolean visible)
1012 SearchData search_data;
1014 search_data.combo = combo;
1015 search_data.visible = visible;
1016 search_data.set = FALSE;
1018 gtk_tree_model_foreach (model, tree_first_func, &search_data);
1020 *first = search_data.iter;
1022 return search_data.set;
1025 static gboolean
1026 egg_combo_select_key_press (GtkWidget * widget, GdkEventKey * event, gpointer data)
1028 EggComboSelect *combo_select = EGG_COMBO_SELECT (data);
1029 guint state = event->state & gtk_accelerator_get_default_mod_mask ();
1030 gboolean found;
1031 GtkTreeIter iter;
1032 GtkTreeIter new_iter;
1033 GtkTreeSelection *selection;
1035 if (combo_select->priv->model == NULL)
1036 return FALSE;
1038 if ((event->keyval == GDK_Down || event->keyval == GDK_KP_Down) &&
1039 state == GDK_MOD1_MASK)
1041 egg_combo_select_popup (combo_select);
1043 return TRUE;
1045 if (event->keyval == GDK_Escape || event->keyval == GDK_Return ||
1046 event->keyval == GDK_space)
1048 egg_combo_select_popdown (combo_select);
1050 return TRUE;
1053 switch (event->keyval)
1055 case GDK_Down:
1056 case GDK_KP_Down:
1057 if (egg_combo_select_get_active_iter (combo_select, &iter))
1059 found = tree_next (combo_select, combo_select->priv->model,
1060 &iter, &new_iter, FALSE);
1061 break;
1063 /* else fall through */
1064 case GDK_Page_Up:
1065 case GDK_KP_Page_Up:
1066 case GDK_Home:
1067 case GDK_KP_Home:
1068 found = tree_first (combo_select, combo_select->priv->model, &new_iter, FALSE);
1069 break;
1071 case GDK_Up:
1072 case GDK_KP_Up:
1073 if (egg_combo_select_get_active_iter (combo_select, &iter))
1075 found = tree_prev (combo_select, combo_select->priv->model,
1076 &iter, &new_iter, FALSE);
1077 break;
1079 /* else fall through */
1080 case GDK_Page_Down:
1081 case GDK_KP_Page_Down:
1082 case GDK_End:
1083 case GDK_KP_End:
1084 found = tree_last (combo_select, combo_select->priv->model, &new_iter, FALSE);
1085 break;
1086 default:
1087 return FALSE;
1090 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (combo_select->priv->treeview));
1091 g_signal_handlers_block_by_func (G_OBJECT (selection),
1092 G_CALLBACK (on_treeview_selection_changed), combo_select);
1093 if (found)
1094 egg_combo_select_set_active_iter (combo_select, &new_iter);
1095 g_signal_handlers_unblock_by_func (G_OBJECT (selection),
1096 G_CALLBACK (on_treeview_selection_changed), combo_select);
1098 return TRUE;
1102 static gint
1103 egg_combo_select_button_press (GtkWidget * widget, GdkEvent * event, gpointer data)
1105 GtkWidget *child;
1107 child = gtk_get_event_widget (event);
1109 if (child != widget)
1111 while (child)
1113 if (child == widget)
1114 return FALSE;
1115 child = child->parent;
1118 egg_combo_select_popdown (EGG_COMBO_SELECT (data));
1120 gtk_widget_hide (widget);
1121 gtk_grab_remove (widget);
1122 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1123 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(EGG_COMBO_SELECT(data)->priv->button), FALSE);
1125 return TRUE;
1128 static void
1129 egg_combo_select_size_request (GtkWidget *widget,
1130 GtkRequisition *requisition)
1132 EggComboSelect *combo_select;
1133 GtkRequisition box_requisition;
1135 g_return_if_fail (widget != NULL);
1136 g_return_if_fail (EGG_IS_COMBO_SELECT (widget));
1137 g_return_if_fail (requisition != NULL);
1139 GTK_WIDGET_CLASS (parent_class)->size_request (widget, &box_requisition);
1141 combo_select=EGG_COMBO_SELECT(widget);
1143 size = MIN(box_requisition.width, box_requisition.height);
1144 size = MIN(combo_select->priv->button->requisition.width, box_requisition.height);
1145 size = MIN(combo_select->priv->button->requisition.width, box_requisition.height);
1147 widget->requisition.height = size;
1148 widget->requisition.width = size + combo_select->priv->arrow->requisition.width;
1150 if (box_requisition.width < 200)
1151 box_requisition.width = 200;
1152 widget->requisition.height = box_requisition.height;
1153 widget->requisition.width = box_requisition.width;
1156 static void
1157 egg_combo_select_size_allocate (GtkWidget *widget,
1158 GtkAllocation *allocation)
1160 EggComboSelect *combo_select;
1161 GtkAllocation button_allocation;
1163 g_return_if_fail (widget != NULL);
1164 g_return_if_fail (EGG_IS_COMBO_SELECT (widget));
1165 g_return_if_fail (allocation != NULL);
1167 GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
1169 combo_select = EGG_COMBO_SELECT (widget);
1171 button_allocation = combo_select->priv->button->allocation;
1173 button_allocation.width = MIN(button_allocation.width,
1174 combo_select->priv->button->requisition.width);
1175 button_allocation.height = MIN(button_allocation.height,
1176 combo_select->priv->button->requisition.height);
1177 button_allocation.x += (combo_select->priv->button->allocation.width-
1178 button_allocation.width) / 2;
1179 button_allocation.y += (combo_select->priv->button->allocation.height-
1180 button_allocation.height) / 2;
1182 if (button_allocation.width < allocation->width -
1183 combo_select->priv->arrow->requisition.width)
1185 button_allocation.width =
1186 allocation->width - combo_select->priv->arrow->requisition.width;
1188 gtk_widget_size_allocate (combo_select->priv->button, &button_allocation);
1190 button_allocation.x = combo_select->priv->button->allocation.x +
1191 combo_select->priv->button->allocation.width;
1192 button_allocation.width = combo_select->priv->arrow->requisition.width;
1193 gtk_widget_size_allocate (combo_select->priv->arrow, &button_allocation);
1196 void
1197 egg_combo_select_set_model (EggComboSelect *combo_select, GtkTreeModel *model)
1199 g_return_if_fail (EGG_IS_COMBO_SELECT (combo_select));
1200 g_return_if_fail (GTK_IS_TREE_MODEL (model));
1202 if (combo_select->priv->model == NULL)
1203 egg_combo_select_sync_cells (combo_select,
1204 GTK_CELL_LAYOUT (combo_select->priv->cell_view));
1206 g_object_ref (model);
1208 /* Unset model */
1209 if (combo_select->priv->active_row)
1211 gtk_tree_row_reference_free (combo_select->priv->active_row);
1212 combo_select->priv->active_row = NULL;
1215 if (combo_select->priv->model)
1217 g_object_unref (combo_select->priv->model);
1218 combo_select->priv->model = NULL;
1221 /* Set model */
1222 combo_select->priv->model = model;
1223 if (combo_select->priv->treeview)
1224 gtk_tree_view_set_model (GTK_TREE_VIEW (combo_select->priv->treeview),
1225 model);
1226 gtk_cell_view_set_model (GTK_CELL_VIEW (combo_select->priv->cell_view),
1227 model);
1228 /*if (combo_select->priv->cell_view)
1229 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (combo_select->priv->cell_view),
1230 NULL);
1234 static void
1235 egg_combo_select_set_active_internal (EggComboSelect *combo_select, GtkTreePath *path)
1237 GtkTreePath *active_path;
1238 gint path_cmp;
1240 if (combo_select->priv->model == NULL)
1241 return;
1243 if (path && combo_select->priv->active_row)
1245 active_path = gtk_tree_row_reference_get_path (combo_select->priv->active_row);
1246 if (active_path)
1248 path_cmp = gtk_tree_path_compare (path, active_path);
1249 gtk_tree_path_free (active_path);
1250 if (path_cmp == 0)
1251 return;
1255 if (combo_select->priv->active_row)
1257 gtk_tree_row_reference_free (combo_select->priv->active_row);
1258 combo_select->priv->active_row = NULL;
1261 if (!path)
1263 if (combo_select->priv->treeview)
1264 gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (combo_select->priv->treeview)));
1266 if (combo_select->priv->cell_view)
1267 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (combo_select->priv->cell_view), NULL);
1269 else
1271 combo_select->priv->active_row =
1272 gtk_tree_row_reference_new (combo_select->priv->model, path);
1274 if (combo_select->priv->treeview)
1276 gtk_tree_view_set_cursor (GTK_TREE_VIEW (combo_select->priv->treeview),
1277 path, NULL, FALSE);
1280 if (combo_select->priv->cell_view)
1281 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (combo_select->priv->cell_view),
1282 path);
1284 g_signal_emit_by_name (combo_select, "changed", NULL, NULL);
1287 void
1288 egg_combo_select_set_active (EggComboSelect *combo_select, gint iter_index)
1290 GtkTreePath *path = NULL;
1291 g_return_if_fail (EGG_IS_COMBO_SELECT (combo_select));
1292 g_return_if_fail (iter_index >= -1);
1294 if (combo_select->priv->model == NULL)
1295 return;
1297 if (iter_index != -1)
1298 path = gtk_tree_path_new_from_indices (iter_index, -1);
1300 egg_combo_select_set_active_internal (combo_select, path);
1302 if (path)
1303 gtk_tree_path_free (path);
1306 gint
1307 egg_combo_select_get_active (EggComboSelect *combo_select)
1309 gint result;
1310 g_return_val_if_fail (EGG_IS_COMBO_SELECT (combo_select), 0);
1312 if (combo_select->priv->active_row)
1314 GtkTreePath *path;
1315 path = gtk_tree_row_reference_get_path (combo_select->priv->active_row);
1316 if (path == NULL)
1317 result = -1;
1318 else
1320 result = gtk_tree_path_get_indices (path)[0];
1321 gtk_tree_path_free (path);
1324 else
1325 result = -1;
1327 return result;
1330 void
1331 egg_combo_select_set_active_iter (EggComboSelect *combo_select,
1332 GtkTreeIter *iter)
1334 GtkTreePath *path;
1336 g_return_if_fail (EGG_IS_COMBO_SELECT (combo_select));
1337 if (combo_select->priv->model == NULL)
1338 return;
1339 path = gtk_tree_model_get_path (combo_select->priv->model, iter);
1340 egg_combo_select_set_active_internal (combo_select, path);
1341 gtk_tree_path_free (path);
1344 gboolean
1345 egg_combo_select_get_active_iter (EggComboSelect *combo_select,
1346 GtkTreeIter *iter)
1348 GtkTreePath *path;
1349 gboolean result;
1351 g_return_val_if_fail (EGG_IS_COMBO_SELECT (combo_select), FALSE);
1353 if (!combo_select->priv->active_row)
1354 return FALSE;
1355 path = gtk_tree_row_reference_get_path (combo_select->priv->active_row);
1356 if (!path)
1357 return FALSE;
1358 result = gtk_tree_model_get_iter (combo_select->priv->model, iter, path);
1359 gtk_tree_path_free (path);
1361 return result;
1364 void
1365 egg_combo_select_set_title (EggComboSelect *combo_select, const gchar *title)
1367 g_free (combo_select->priv->title);
1368 combo_select->priv->title = g_strdup (title);
1369 if (combo_select->priv->column)
1370 gtk_tree_view_column_set_title (combo_select->priv->column, title);