glade-inspector.c 34.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/*
 * glade-inspector.h
 *
 * Copyright (C) 2001 Ximian, Inc.
 * Copyright (C) 2007 Vincent Geddes
 *
 * Authors:
 *   Chema Celorio
 *   Tristan Van Berkom <tvb@gnome.org>
 *   Vincent Geddes <vincent.geddes@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <config.h>

29 30 31 32 33 34 35 36 37 38
/**
 * SECTION:glade-inspector
 * @Short_Description: A widget for inspecting objects in a #GladeProject.
 *
 * A #GladeInspector is a widget for inspecting the objects that make up a user interface. 
 *
 * An inspector is created by calling either glade_inspector_new() or glade_inspector_new_with_project(). 
 * The current project been inspected can be changed by calling glade_inspector_set_project().
 */

39 40 41 42 43 44 45
#include "glade.h"
#include "glade-widget.h"
#include "glade-project.h"
#include "glade-widget-adaptor.h"
#include "glade-inspector.h"
#include "glade-popup.h"
#include "glade-app.h"
Juan Pablo Ugarte's avatar
Juan Pablo Ugarte committed
46
#include "glade-dnd.h"
47 48 49

#include <string.h>
#include <glib/gi18n-lib.h>
50 51 52
#if GTK_CHECK_VERSION (2, 21, 8)
#include <gdk/gdkkeysyms-compat.h>
#else
53
#include <gdk/gdkkeysyms.h>
54
#endif
55 56 57 58 59

#define GLADE_INSPECTOR_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object),\
					    GLADE_TYPE_INSPECTOR,                 \
			 		    GladeInspectorPrivate))

60

61 62 63 64
static void     search_entry_text_inserted_cb (GtkEntry       *entry,
					       const gchar    *text,
					       gint            length,
					       gint           *position,
65
					       GladeInspector *inspector);
66 67 68
static void     search_entry_text_deleted_cb  (GtkEditable    *editable,
					       gint            start_pos,
					       gint            end_pos,
69 70
					       GladeInspector *inspector);

71 72
enum
{
73
  PROP_0,
74 75
  PROP_PROJECT,
  N_PROPERTIES
76 77 78 79
};

enum
{
80 81 82 83
  SELECTION_CHANGED,
  ITEM_ACTIVATED,
  LAST_SIGNAL
};
84 85 86

struct _GladeInspectorPrivate
{
87 88
  GtkWidget *view;
  GtkTreeModel *filter;
89

90
  GladeProject *project;
91 92 93 94

  GtkWidget *entry;
  guint idle_complete;
  gboolean search_disabled;
95
  gchar *completion_text;
96
  gchar *completion_text_fold;
97 98
};

99
static GParamSpec *properties[N_PROPERTIES];
100
static guint glade_inspector_signals[LAST_SIGNAL] = { 0 };
101 102


103 104 105
static void glade_inspector_dispose (GObject *object);
static void glade_inspector_finalize (GObject *object);
static void add_columns (GtkTreeView *inspector);
106 107
static void item_activated_cb (GtkTreeView       *view,
                               GtkTreePath       *path,
108
                               GtkTreeViewColumn *column,
109
                               GladeInspector    *inspector);
110
static void selection_changed_cb (GtkTreeSelection *selection,
111 112
                                  GladeInspector   *inspector);
static gint button_press_cb (GtkWidget      *widget,
113 114
                             GdkEventButton *event,
                             GladeInspector *inspector);
115

116
G_DEFINE_TYPE_WITH_PRIVATE (GladeInspector, glade_inspector, GTK_TYPE_BOX)
117 118

static void
119 120 121 122
glade_inspector_set_property (GObject      *object,
			      guint         property_id,
			      const GValue *value,
                              GParamSpec   *pspec)
123
{
124
  GladeInspector *inspector = GLADE_INSPECTOR (object);
125

126 127 128 129 130 131 132 133 134
  switch (property_id)
    {
      case PROP_PROJECT:
        glade_inspector_set_project (inspector, g_value_get_object (value));
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
135 136 137
}

static void
138 139 140
glade_inspector_get_property (GObject    *object,
                              guint       property_id,
                              GValue     *value,
141
                              GParamSpec *pspec)
142
{
143
  GladeInspector *inspector = GLADE_INSPECTOR (object);
144

145 146 147 148 149 150 151 152 153
  switch (property_id)
    {
      case PROP_PROJECT:
        g_value_set_object (value, glade_inspector_get_project (inspector));
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
154 155 156
}

static void
157
glade_inspector_class_init (GladeInspectorClass *klass)
158
{
159
  GObjectClass *object_class;
160

161
  object_class = G_OBJECT_CLASS (klass);
162

163 164 165 166 167
  object_class->dispose = glade_inspector_dispose;
  object_class->finalize = glade_inspector_finalize;
  object_class->set_property = glade_inspector_set_property;
  object_class->get_property = glade_inspector_get_property;

168 169 170 171 172 173
  /**
   * GladeInspector::selection-changed:
   * @inspector: the object which received the signal
   *
   * Emitted when the selection changes in the GladeInspector.
   */
174 175 176 177 178 179 180
  glade_inspector_signals[SELECTION_CHANGED] =
      g_signal_new ("selection-changed",
                    G_TYPE_FROM_CLASS (object_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (GladeInspectorClass, selection_changed),
                    NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

181 182 183 184 185 186
  /**
   * GladeInspector::item-activated:
   * @inspector: the object which received the signal
   *
   * Emitted when a item is activated in the GladeInspector.
   */
187 188 189 190 191 192 193
  glade_inspector_signals[ITEM_ACTIVATED] =
      g_signal_new ("item-activated",
                    G_TYPE_FROM_CLASS (object_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (GladeInspectorClass, item_activated),
                    NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

194 195 196 197 198 199
  properties[PROP_PROJECT] =
    g_param_spec_object ("project",
                         _("Project"),
                         _("The project being inspected"),
                         GLADE_TYPE_PROJECT,
                         G_PARAM_READABLE | G_PARAM_WRITABLE);
200
  
201 202
  /* Install all properties */
  g_object_class_install_properties (object_class, N_PROPERTIES, properties);
203 204
}

205
static gboolean
206
glade_inspector_visible_func (GtkTreeModel *model,
207 208
                              GtkTreeIter  *parent,
                              gpointer      data)
209 210 211 212 213 214 215 216
{
  GladeInspector *inspector = data;
  GladeInspectorPrivate *priv = inspector->priv;

  GtkTreeIter iter;

  gboolean retval = FALSE;

217
  if (priv->search_disabled || priv->completion_text == NULL)
218 219 220 221 222 223 224 225 226 227
    return TRUE;

  if (gtk_tree_model_iter_children (model, &iter, parent))
    {
      do
        {
          retval = glade_inspector_visible_func (model, &iter, data);
        }
      while (gtk_tree_model_iter_next (model, &iter) && !retval);
    }
228

229 230
  if (!retval)
    {
231
      gchar *widget_name, *haystack;
232 233 234 235

      gtk_tree_model_get (model, parent, GLADE_PROJECT_MODEL_COLUMN_NAME,
                          &widget_name, -1);

236 237 238
      haystack = g_utf8_casefold (widget_name, -1);

      retval = strstr (haystack, priv->completion_text_fold) != NULL;
239

240
      g_free (haystack);
241
      g_free (widget_name);
242 243 244
    }

  return retval;
245 246
}

247
static void
248
glade_inspector_refilter (GladeInspector *inspector)
249 250 251 252 253 254 255 256 257 258 259 260 261
{
  GladeInspectorPrivate *priv = inspector->priv;

  if (!priv->search_disabled)
    {
      gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
      gtk_tree_view_expand_all (GTK_TREE_VIEW (priv->view));
    }
}

static void
search_entry_changed_cb (GtkEntry *entry, GladeInspector *inspector)
{
262
  glade_inspector_refilter (inspector);
263 264
}

265
typedef struct {
266 267 268
  const gchar    *text;
  gchar          *common_text;
  gchar          *first_match;
269 270
} CommonMatchData;

271
static void
272
reduce_string (gchar *str1,
273
	       const gchar *str2)
274
{
275 276 277 278
  gint str1len = strlen (str1);
  gint i;

  for (i = 0; str2[i] != '\0'; i++)
279
    {
280 281 282 283 284 285

      if (str1[i] != str2[i] || i >= str1len)
	{
	  str1[i] = '\0';
	  break;
	}
286
    }
287 288 289 290 291 292

  if (str2[i] == '\0')
    str1[i] = '\0';
}

static gboolean
293 294 295 296
search_common_matches (GtkTreeModel    *model,
		       GtkTreePath     *path,
		       GtkTreeIter     *iter,
		       CommonMatchData *data)
297
{
298 299 300
  GladeWidget *gwidget;
  const gchar *name;
  GObject *obj;
301 302
  gboolean match;

303 304
  gtk_tree_model_get (model, iter, GLADE_PROJECT_MODEL_COLUMN_OBJECT, &obj, -1);
  gwidget = glade_widget_get_from_gobject (obj);
305

306
  if (!glade_widget_has_name (gwidget))
307
    {
308
      g_object_unref (obj);
309 310 311
      return FALSE;
    }

312 313
  name  = glade_widget_get_name (gwidget);
  match = (strncmp (data->text, name, strlen (data->text)) == 0);
314 315 316

  if (match)
    {
317
      if (!data->first_match)
318
	data->first_match = g_strdup (name);
319

320
      if (data->common_text)
321
        reduce_string (data->common_text, name);
322
      else
323
	data->common_text = g_strdup (name);
324 325
    }

326
  g_object_unref (obj);
327
  return FALSE;
328 329
}

330 331 332 333 334 335
/* Returns the shortest common matching text from all
 * project widget names.
 *
 * If shortest_match is specified, it is given the first
 * full match for the 'search' text
 */
336
static gchar *
337
get_partial_match (GladeInspector *inspector,
338 339
		   const gchar    *search,
		   gchar         **first_match)
340
{
341
  GtkTreeModel     *model = GTK_TREE_MODEL (inspector->priv->project);
342 343 344 345
  CommonMatchData   data;

  data.text        = search;
  data.common_text = NULL;
346
  data.first_match = NULL;
347
  
348
  gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)search_common_matches, &data);
349

350 351 352 353 354
  if (first_match)
    *first_match = data.first_match;
  else
    g_free (data.first_match);

355
  return data.common_text;
356 357
}

358 359 360 361 362 363 364 365 366 367 368
static void
inspector_set_completion_text (GladeInspector *inspector, const gchar *text)
{
  GladeInspectorPrivate *priv = inspector->priv;

  g_free (priv->completion_text);
  priv->completion_text = g_strdup (text);
  priv->completion_text_fold = text ? g_utf8_casefold (text, -1) : NULL;

}

369 370 371 372
static gboolean
search_complete_idle (GladeInspector *inspector)
{
  GladeInspectorPrivate *priv = inspector->priv;
373
  gchar *completed;
374 375 376 377 378
  const gchar *str;
  gsize length;

  str = gtk_entry_get_text (GTK_ENTRY (priv->entry));

379
  completed = get_partial_match (inspector, str, NULL);
380
  
381
  inspector_set_completion_text (inspector, str);
382

383 384 385 386
  if (completed)
    {
      length = strlen (str);

387 388 389
      g_signal_handlers_block_by_func (priv->entry, search_entry_text_inserted_cb, inspector);
      g_signal_handlers_block_by_func (priv->entry, search_entry_text_deleted_cb, inspector);

390 391 392 393
      gtk_entry_set_text (GTK_ENTRY (priv->entry), completed);
      gtk_editable_set_position (GTK_EDITABLE (priv->entry), length);
      gtk_editable_select_region (GTK_EDITABLE (priv->entry), length, -1);
      g_free (completed);
394 395 396 397

      g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_inserted_cb, inspector);
      g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_deleted_cb, inspector);

398 399 400 401 402 403 404 405
    }

  priv->idle_complete = 0;

  return FALSE;
}

static void
406 407 408 409
search_entry_text_inserted_cb (GtkEntry       *entry,
                               const gchar    *text,
                               gint            length,
                               gint           *position,
410 411 412 413 414 415 416 417 418 419 420
                               GladeInspector *inspector)
{
  GladeInspectorPrivate *priv = inspector->priv;

  if (!priv->search_disabled && !priv->idle_complete)
    {
      priv->idle_complete =
          g_idle_add ((GSourceFunc) search_complete_idle, inspector);
    }
}

421
static void
422 423 424
search_entry_text_deleted_cb (GtkEditable    *editable,
			      gint            start_pos,
			      gint            end_pos,
425 426 427 428 429 430
			      GladeInspector *inspector)
{
  GladeInspectorPrivate *priv = inspector->priv;

  if (!priv->search_disabled)
    {
431
      inspector_set_completion_text (inspector, gtk_entry_get_text (GTK_ENTRY (priv->entry)));
432 433 434 435
      glade_inspector_refilter (inspector);
    }
}

436
static gboolean
437 438
search_entry_key_press_event_cb (GtkEntry       *entry,
                                 GdkEventKey    *event,
439 440 441 442 443
                                 GladeInspector *inspector)
{
  GladeInspectorPrivate *priv = inspector->priv;
  const gchar *str;

444 445
  str = gtk_entry_get_text (GTK_ENTRY (priv->entry));

446 447
  if (event->keyval == GDK_KEY_Tab)
    {
448
      /* CNTL-Tab: An escape route to move focus */
449 450 451 452
      if (event->state & GDK_CONTROL_MASK)
        {
          gtk_widget_grab_focus (priv->view);
        }
453
      else /* Tab: Move cursor forward and refine the filter to include all text */
454
        {
455
          inspector_set_completion_text (inspector, str);
456

457 458
          gtk_editable_set_position (GTK_EDITABLE (entry), -1);
          gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1);
459 460

	  glade_inspector_refilter (inspector);
461 462 463 464
        }
      return TRUE;
    }

465
  /* Enter/Return: Find the first complete match, refine filter to the complete match, and select the match  */
466 467
  if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter)
    {
468
      gchar *name, *full_match = NULL;
469

470
      if (str && (name = get_partial_match (inspector, str, &full_match)))
471
        {
472 473
	  GladeWidget *widget;

474
          inspector_set_completion_text (inspector, full_match);
475
	  g_free (name);
476 477 478 479

	  g_signal_handlers_block_by_func (priv->entry, search_entry_text_inserted_cb, inspector);
	  g_signal_handlers_block_by_func (priv->entry, search_entry_text_deleted_cb, inspector);

480
          gtk_entry_set_text (GTK_ENTRY (entry), priv->completion_text);
481 482 483 484

	  g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_inserted_cb, inspector);
	  g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_deleted_cb, inspector);

485 486
          gtk_editable_set_position (GTK_EDITABLE (entry), -1);
          gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1);
487 488 489 490 491 492

	  glade_inspector_refilter (inspector);

	  widget = glade_project_get_widget_by_name (priv->project, priv->completion_text);
	  if (widget)
	    glade_project_selection_set (priv->project, glade_widget_get_object (widget), TRUE);
493 494 495 496
        }
      return TRUE;
    }

497
  /* Backspace: Attempt to move the cursor backwards and maintain the completed/selected portion */
498 499 500 501 502 503 504 505 506 507
  if (event->keyval == GDK_KEY_BackSpace)
    {
      if (!priv->search_disabled && !priv->idle_complete && str && str[0])
	{
	  /* Now, set the text to the current completion text -1 char, and recomplete */
	  if (priv->completion_text && priv->completion_text[0])
	    {
	      /* If we're not at the position of the length of the completion text, just carry on */
	      if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (priv->entry), NULL, NULL))
		return FALSE;
508

509
	      priv->completion_text[strlen (priv->completion_text) -1] = '\0';
510

511 512
	      g_signal_handlers_block_by_func (priv->entry, search_entry_text_inserted_cb, inspector);
	      g_signal_handlers_block_by_func (priv->entry, search_entry_text_deleted_cb, inspector);
513

514 515
	      gtk_entry_set_text (GTK_ENTRY (priv->entry), priv->completion_text);
	      gtk_editable_set_position (GTK_EDITABLE (priv->entry), -1);
516

517 518
	      g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_inserted_cb, inspector);
	      g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_deleted_cb, inspector);
519

520 521
	      priv->idle_complete =
		g_idle_add ((GSourceFunc) search_complete_idle, inspector);
522

523 524 525
	      return TRUE;
	    }
	}
526
    }
527 528

  return FALSE;
529 530 531
}

static gboolean
532 533
search_entry_focus_in_cb (GtkWidget      *entry,
                          GdkEventFocus  *event,
534 535 536 537 538
                          GladeInspector *inspector)
{
  GladeInspectorPrivate *priv = inspector->priv;

  if (priv->search_disabled)
539
    priv->search_disabled = FALSE;
540 541 542 543 544

  return FALSE;
}

static gboolean
545 546
search_entry_focus_out_cb (GtkWidget      *entry,
                           GdkEventFocus  *event,
547 548
                           GladeInspector *inspector)
{
549 550 551 552
  GladeInspectorPrivate *priv = inspector->priv;

  priv->search_disabled = TRUE;

553
  inspector_set_completion_text (inspector, NULL);
554 555

  gtk_entry_set_text (GTK_ENTRY (priv->entry), "");
556

557 558
  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));

559 560 561
  return FALSE;
}

562
static void
563
glade_inspector_init (GladeInspector *inspector)
564
{
565 566 567 568
  GladeInspectorPrivate *priv;
  GtkWidget *sw;
  GtkTreeSelection *selection;

569
  inspector->priv = priv = glade_inspector_get_instance_private (inspector);
570

571 572 573
  gtk_orientable_set_orientation (GTK_ORIENTABLE (inspector),
				  GTK_ORIENTATION_VERTICAL);

574
  priv->project = NULL;
575

576
  priv->entry = gtk_entry_new ();
577

578
  gtk_entry_set_placeholder_text (GTK_ENTRY (priv->entry), _(" < Search Widgets >"));
579 580 581 582 583 584 585 586 587 588
  gtk_widget_show (priv->entry);
  gtk_box_pack_start (GTK_BOX (inspector), priv->entry, FALSE, FALSE, 2);

  g_signal_connect (priv->entry, "changed",
                    G_CALLBACK (search_entry_changed_cb), inspector);
  g_signal_connect (priv->entry, "key-press-event",
                    G_CALLBACK (search_entry_key_press_event_cb), inspector);
  g_signal_connect_after (priv->entry, "insert-text",
                          G_CALLBACK (search_entry_text_inserted_cb),
                          inspector);
589 590 591
  g_signal_connect_after (priv->entry, "delete-text",
                          G_CALLBACK (search_entry_text_deleted_cb),
                          inspector);
592 593 594 595 596
  g_signal_connect (priv->entry, "focus-in-event",
                    G_CALLBACK (search_entry_focus_in_cb), inspector);
  g_signal_connect (priv->entry, "focus-out-event",
                    G_CALLBACK (search_entry_focus_out_cb), inspector);

597
  priv->view = gtk_tree_view_new ();
598
  gtk_tree_view_set_enable_search (GTK_TREE_VIEW (priv->view), FALSE);
599
  gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (priv->view), GTK_SCROLL_MINIMUM);
600
  add_columns (GTK_TREE_VIEW (priv->view));
601

Juan Pablo Ugarte's avatar
Juan Pablo Ugarte committed
602 603 604 605 606
  /* Set it as a drag source */
  gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (priv->view),
                                          GDK_BUTTON1_MASK,
                                          _glade_dnd_get_target (), 1, 0);

607 608
  g_signal_connect (G_OBJECT (priv->view), "row-activated",
                    G_CALLBACK (item_activated_cb), inspector);
609

610 611 612 613
  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->view));
  gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
  g_signal_connect (G_OBJECT (selection), "changed",
                    G_CALLBACK (selection_changed_cb), inspector);
614

615
  /* Expand All */
616
  gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->entry), GTK_ENTRY_ICON_SECONDARY, "go-down");
617 618 619
  gtk_entry_set_icon_tooltip_text (GTK_ENTRY (priv->entry), GTK_ENTRY_ICON_SECONDARY, _("Expand all"));
  g_signal_connect_swapped (priv->entry, "icon-press", G_CALLBACK (gtk_tree_view_expand_all), priv->view);

620 621 622
  /* popup menu */
  g_signal_connect (G_OBJECT (priv->view), "button-press-event",
                    G_CALLBACK (button_press_cb), inspector);
623

624 625 626 627 628 629 630 631 632
  sw = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
  gtk_container_add (GTK_CONTAINER (sw), priv->view);
  gtk_box_pack_start (GTK_BOX (inspector), sw, TRUE, TRUE, 0);

  gtk_widget_show (priv->view);
  gtk_widget_show (sw);
633 634 635
}

static void
636
glade_inspector_dispose (GObject *object)
637
{
638
  GladeInspector *inspector = GLADE_INSPECTOR (object);
639

640 641
  glade_inspector_set_project (inspector, NULL);

642 643 644 645 646 647
  if (inspector->priv->idle_complete)
    {
      g_source_remove (inspector->priv->idle_complete);
      inspector->priv->idle_complete = 0;
    }

648
  G_OBJECT_CLASS (glade_inspector_parent_class)->dispose (object);
649 650 651
}

static void
652
glade_inspector_finalize (GObject *object)
653
{
654
  GladeInspector *inspector = GLADE_INSPECTOR (object);
655
  GladeInspectorPrivate *priv = inspector->priv;
656

657 658
  g_free (priv->completion_text);
  g_free (priv->completion_text_fold);
659

660
  G_OBJECT_CLASS (glade_inspector_parent_class)->finalize (object);
661 662
}

663
static void
664
project_selection_changed_cb (GladeProject   *project,
665
                              GladeInspector *inspector)
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
{
  GladeWidget *widget;
  GtkTreeSelection *selection;
  GtkTreeModel *model;
  GtkTreeIter *iter;
  GtkTreePath *path, *ancestor_path;
  GList *list;

  g_return_if_fail (GLADE_IS_INSPECTOR (inspector));
  g_return_if_fail (GLADE_IS_PROJECT (project));
  g_return_if_fail (inspector->priv->project == project);

  g_signal_handlers_block_by_func (gtk_tree_view_get_selection
                                   (GTK_TREE_VIEW (inspector->priv->view)),
                                   G_CALLBACK (selection_changed_cb),
                                   inspector);

  selection =
      gtk_tree_view_get_selection (GTK_TREE_VIEW (inspector->priv->view));
  g_return_if_fail (selection != NULL);

  model = inspector->priv->filter;

  gtk_tree_selection_unselect_all (selection);

  for (list = glade_project_selection_get (project);
       list && list->data; list = list->next)
    {
      if ((widget =
           glade_widget_get_from_gobject (G_OBJECT (list->data))) != NULL)
        {
          if ((iter =
               glade_util_find_iter_by_widget (model, widget,
                                               GLADE_PROJECT_MODEL_COLUMN_OBJECT))
              != NULL)
            {
              path = gtk_tree_model_get_path (model, iter);
              ancestor_path = gtk_tree_path_copy (path);

              /* expand parent node */
              if (gtk_tree_path_up (ancestor_path))
                gtk_tree_view_expand_to_path
                    (GTK_TREE_VIEW (inspector->priv->view), ancestor_path);

              gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW
                                            (inspector->priv->view), path, NULL,
                                            TRUE, 0.5, 0);

              gtk_tree_selection_select_iter (selection, iter);

              gtk_tree_iter_free (iter);
              gtk_tree_path_free (path);
              gtk_tree_path_free (ancestor_path);
            }
        }
    }

  g_signal_handlers_unblock_by_func (gtk_tree_view_get_selection
                                     (GTK_TREE_VIEW (inspector->priv->view)),
                                     G_CALLBACK (selection_changed_cb),
                                     inspector);
727 728
}

729
static void
730
selection_foreach_func (GtkTreeModel *model,
731 732 733
                        GtkTreePath  *path,
                        GtkTreeIter  *iter,
                        GList       **selection)
734
{
735 736 737 738
  GObject *object;

  gtk_tree_model_get (model, iter, GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object,
                      -1);
739

740 741 742 743 744
  if (object)
    {
      *selection = g_list_prepend (*selection, object);
      g_object_unref (object);
    }
745 746 747
}

static void
748
selection_changed_cb (GtkTreeSelection *selection, GladeInspector *inspector)
749
{
750
  GList *sel = NULL, *l;
751

752 753 754
  gtk_tree_selection_selected_foreach (selection,
                                       (GtkTreeSelectionForeachFunc)
                                       selection_foreach_func, &sel);
755

756 757 758 759 760 761 762 763 764 765 766
  /* We dont modify the project selection for a change that
   * leaves us with no selection. 
   *
   * This is typically because the user is changing the name
   * of a widget and the filter is active, the new name causes
   * the row to go out of the model and the selection to be
   * cleared, if we clear the selection we remove the editor
   * that the user is trying to type into.
   */
  if (!sel)
    return;
767

768 769 770
  g_signal_handlers_block_by_func (inspector->priv->project,
                                   G_CALLBACK (project_selection_changed_cb),
                                   inspector);
771

772
  glade_project_selection_clear (inspector->priv->project, FALSE);
773
  for (l = sel; l; l = l->next)
774 775
    glade_project_selection_add (inspector->priv->project, G_OBJECT (l->data), FALSE);
  glade_project_selection_changed (inspector->priv->project);
776
  g_list_free (sel);
777

778 779 780
  g_signal_handlers_unblock_by_func (inspector->priv->project,
                                     G_CALLBACK (project_selection_changed_cb),
                                     inspector);
781

782
  g_signal_emit (inspector, glade_inspector_signals[SELECTION_CHANGED], 0);
783 784 785
}

static void
786 787
item_activated_cb (GtkTreeView       *view,
                   GtkTreePath       *path,
788
                   GtkTreeViewColumn *column,
789
                   GladeInspector    *inspector)
790 791
{
  g_signal_emit (inspector, glade_inspector_signals[ITEM_ACTIVATED], 0);
792 793 794
}

static gint
795
button_press_cb (GtkWidget      *widget,
796 797
                 GdkEventButton *event,
                 GladeInspector *inspector)
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
{
  GtkTreeView *view = GTK_TREE_VIEW (widget);
  GladeInspectorPrivate *priv = inspector->priv;
  GtkTreePath *path = NULL;
  gboolean handled = FALSE;

  /* Give some kind of access in case of missing right button */
  if (event->window == gtk_tree_view_get_bin_window (view) &&
      glade_popup_is_popup_event (event))
    {
      if (gtk_tree_view_get_path_at_pos (view, (gint) event->x, (gint) event->y,
                                         &path, NULL,
                                         NULL, NULL) && path != NULL)
        {
          GtkTreeIter iter;
          GladeWidget *object = NULL;
          if (gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->project),
                                       &iter, path))
            {
              /* now we can obtain the widget from the iter.
               */
              gtk_tree_model_get (GTK_TREE_MODEL (priv->project), &iter,
                                  GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object,
                                  -1);

              if (widget != NULL)
                glade_popup_widget_pop (glade_widget_get_from_gobject (object),
                                        event, TRUE);
              else
827
                glade_popup_simple_pop (priv->project, event);
828 829 830 831 832 833 834 835

              handled = TRUE;

              gtk_tree_path_free (path);
            }
        }
      else
        {
836
          glade_popup_simple_pop (priv->project, event);
837 838 839 840
          handled = TRUE;
        }
    }
  return handled;
841 842
}

843 844
static void
glade_inspector_warning_cell_data_func (GtkTreeViewColumn *column,
845 846 847 848
					GtkCellRenderer   *renderer,
					GtkTreeModel      *model,
					GtkTreeIter       *iter,
					gpointer           data)
849 850 851 852 853 854 855 856 857 858 859 860
{
  gchar *warning = NULL;

  gtk_tree_model_get (model, iter,
		      GLADE_PROJECT_MODEL_COLUMN_WARNING, &warning,
		      -1);

  g_object_set (renderer, "visible", warning != NULL, NULL);

  g_free (warning);
}

861 862 863 864 865 866 867
static void
glade_inspector_name_cell_data_func (GtkTreeViewColumn *column,
				     GtkCellRenderer   *renderer,
				     GtkTreeModel      *model,
				     GtkTreeIter       *iter,
				     gpointer           data)
{
868 869
  GladeWidget *gwidget;
  GObject *obj;
870 871

  gtk_tree_model_get (model, iter,
872
		      GLADE_PROJECT_MODEL_COLUMN_OBJECT, &obj,
873 874
		      -1);

875 876
  gwidget = glade_widget_get_from_gobject (obj);

877
  g_object_set (renderer, "text", 
878 879
		(glade_widget_has_name (gwidget)) ? 
		  glade_widget_get_display_name (gwidget) : NULL,
880 881
		NULL);

882
  g_object_unref (obj);
883 884 885
}


886 887
static void
glade_inspector_detail_cell_data_func (GtkTreeViewColumn *column,
888 889 890 891
				       GtkCellRenderer   *renderer,
				       GtkTreeModel      *model,
				       GtkTreeIter       *iter,
				       gpointer           data)
892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917
{
  gchar *type_name = NULL, *detail = NULL;

  gtk_tree_model_get (model, iter,
		      GLADE_PROJECT_MODEL_COLUMN_TYPE_NAME, &type_name,
		      GLADE_PROJECT_MODEL_COLUMN_MISC, &detail,
		      -1);

  if (detail)
    {
      gchar *final = g_strconcat (type_name, "  ", detail, NULL);

      g_object_set (renderer, "text", final, NULL);

      g_free (final);
    }
  else
      g_object_set (renderer, "text", type_name, NULL);

  g_free (type_name);
  g_free (detail);
}




918
static void
919
add_columns (GtkTreeView *view)
920 921 922
{
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;
923
  GtkCellAreaBox *box;
924

925 926
  /* Use a GtkCellArea box to set the alignments manually */
  box = (GtkCellAreaBox *)gtk_cell_area_box_new ();
927

928 929 930 931
  column = gtk_tree_view_column_new_with_area (GTK_CELL_AREA (box));
  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
  gtk_cell_area_box_set_spacing (GTK_CELL_AREA_BOX (box), 2);

932 933 934 935 936 937
  gtk_tree_view_set_tooltip_column (view, GLADE_PROJECT_MODEL_COLUMN_WARNING);

  /* Padding */
  renderer = gtk_cell_renderer_text_new ();
  g_object_set (G_OBJECT (renderer), "width", 4, NULL);
  gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE);
938

939
  /* Warning cell */
940
  renderer = gtk_cell_renderer_pixbuf_new ();
941 942 943 944 945 946 947 948
  g_object_set (renderer,
		"stock-id", "gtk-dialog-warning",
		"xpad", 2,
		NULL);
  gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE);
  gtk_tree_view_column_set_cell_data_func (column, renderer,
					   glade_inspector_warning_cell_data_func,
					   NULL, NULL);
949

950 951 952 953 954
  /* Class Icon */
  renderer = gtk_cell_renderer_pixbuf_new ();
  g_object_set (renderer,
		"xpad", 2,
		NULL);
955
  gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE);
956 957 958 959 960 961
  gtk_tree_view_column_set_attributes (column,
                                       renderer,
                                       "icon_name",
                                       GLADE_PROJECT_MODEL_COLUMN_ICON_NAME,
                                       NULL);

962
  /* Widget Name */
963
  renderer = gtk_cell_renderer_text_new ();
964
  gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE);
965 966 967 968
  gtk_tree_view_column_set_attributes (column,
                                       renderer,
                                       "text", GLADE_PROJECT_MODEL_COLUMN_NAME,
                                       NULL);
969 970 971
  gtk_tree_view_column_set_cell_data_func (column, renderer,
					   glade_inspector_name_cell_data_func,
					   NULL, NULL);
972

973
  /* Padding */
974
  renderer = gtk_cell_renderer_text_new ();
975 976 977
  g_object_set (G_OBJECT (renderer), "width", 8, NULL);
  gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE);

978
  /* Class name & detail */
979 980 981 982
  renderer = gtk_cell_renderer_text_new ();
  g_object_set (G_OBJECT (renderer),
                "style", PANGO_STYLE_ITALIC,
		"foreground", "Gray",
983
		"ellipsize", PANGO_ELLIPSIZE_END,
984
		NULL);
985

986
  gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE);
987 988 989
  gtk_tree_view_column_set_cell_data_func (column, renderer,
					   glade_inspector_detail_cell_data_func,
					   NULL, NULL);
990 991 992

  gtk_tree_view_append_column (view, column);
  gtk_tree_view_set_headers_visible (view, FALSE);
993 994
}

995
static void
996
disconnect_project_signals (GladeInspector *inspector, GladeProject *project)
997 998 999 1000 1001
{
  g_signal_handlers_disconnect_by_func (G_OBJECT (project),
                                        G_CALLBACK
                                        (project_selection_changed_cb),
                                        inspector);
1002 1003 1004
}

static void
1005 1006
connect_project_signals (GladeInspector *inspector, GladeProject *project)
{
1007 1008
  g_signal_connect (G_OBJECT (project), "selection-changed",
                    G_CALLBACK (project_selection_changed_cb), inspector);
1009 1010
}

1011 1012 1013
/**
 * glade_inspector_set_project:
 * @inspector: a #GladeInspector
1014
 * @project: a #GladeProject
1015
 *
1016 1017
 * Sets the current project of @inspector to @project. To unset the current
 * project, pass %NULL for @project.
1018 1019
 */
void
1020
glade_inspector_set_project (GladeInspector *inspector, GladeProject *project)
1021
{
1022 1023 1024 1025 1026
  g_return_if_fail (GLADE_IS_INSPECTOR (inspector));
  g_return_if_fail (GLADE_IS_PROJECT (project) || project == NULL);

  GladeInspectorPrivate *priv = inspector->priv;

1027 1028 1029
  if(priv->project == project)
    return;

1030 1031 1032 1033 1034 1035 1036 1037 1038
  if (inspector->priv->project)
    {
      disconnect_project_signals (inspector, inspector->priv->project);

      /* Release our filter which releases the project */
      gtk_tree_view_set_model (GTK_TREE_VIEW (priv->view), NULL);
      priv->filter = NULL;
      priv->project = NULL;
    }
1039

1040 1041 1042
  if (project)
    {
      priv->project = project;
1043

1044 1045 1046
      /* The filter holds our reference to 'project' */
      priv->filter =
          gtk_tree_model_filter_new (GTK_TREE_MODEL (priv->project), NULL);
1047

1048 1049 1050 1051 1052
      gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER
                                              (priv->filter),
                                              (GtkTreeModelFilterVisibleFunc)
                                              glade_inspector_visible_func,
                                              inspector, NULL);
1053

1054 1055
      gtk_tree_view_set_model (GTK_TREE_VIEW (priv->view), priv->filter);
      g_object_unref (priv->filter);    /* pass ownership of the filter to the model */
1056

1057
      connect_project_signals (inspector, project);
1058
    }
1059

1060
  g_object_notify_by_pspec (G_OBJECT (inspector), properties[PROP_PROJECT]);
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071
}

/**
 * glade_inspector_get_project:
 * @inspector: a #GladeInspector
 * 
 * Note that the method does not ref the returned #GladeProject. 
 *
 * Returns: A #GladeProject
 */
GladeProject *
1072
glade_inspector_get_project (GladeInspector *inspector)
1073
{
1074
  g_return_val_if_fail (GLADE_IS_INSPECTOR (inspector), NULL);
1075

1076
  return inspector->priv->project;
1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
}

/**
 * glade_inspector_get_selected_items:
 * @inspector: a #GladeInspector
 * 
 * Returns the selected items in the inspector. 
 *
 * Returns: A #GList
 */
GList *
1088
glade_inspector_get_selected_items (GladeInspector *inspector)
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118
{
  GtkTreeSelection *selection;
  GList *items = NULL, *paths;
  GladeInspectorPrivate *priv = inspector->priv;

  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->view));

  for (paths = gtk_tree_selection_get_selected_rows (selection, NULL);
       paths != NULL; paths = g_list_next (paths->next))
    {
      GtkTreeIter filter_iter;
      GtkTreeIter iter;
      GtkTreePath *path = (GtkTreePath *) paths->data;
      GObject *object = NULL;

      gtk_tree_model_get_iter (priv->filter, &filter_iter, path);
      gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER
                                                        (priv->filter), &iter,
                                                        &filter_iter);
      gtk_tree_model_get (GTK_TREE_MODEL (priv->project), &iter,
                          GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object, -1);

      g_object_unref (object);
      items = g_list_prepend (items, glade_widget_get_from_gobject (object));
    }

  g_list_foreach (paths, (GFunc) gtk_tree_path_free, NULL);
  g_list_free (paths);

  return items;
1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130
}

/**
 * glade_inspector_new:
 * 
 * Creates a new #GladeInspector
 * 
 * Returns: a new #GladeInspector
 */
GtkWidget *
glade_inspector_new (void)
{
1131
  return g_object_new (GLADE_TYPE_INSPECTOR, NULL);
1132 1133
}

1134 1135 1136 1137 1138 1139 1140 1141 1142
/**
 * glade_inspector_new_with_project:
 * @project: a #GladeProject
 *
 * Creates a new #GladeInspector with @project
 * 
 * Returns: a new #GladeInspector
 */
GtkWidget *
1143
glade_inspector_new_with_project (GladeProject *project)
1144
{
1145 1146
  GladeInspector *inspector;
  g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);
1147

1148
  inspector = g_object_new (GLADE_TYPE_INSPECTOR, "project", project, NULL);
1149

1150 1151
  /* Make sure we expended to the right path */
  project_selection_changed_cb (project, inspector);
1152

1153 1154
  return GTK_WIDGET (inspector);
}