gtksocket.c 45.7 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2 3 4
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6 7 8 9 10 11
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15 16 17 18 19 20
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* By Owen Taylor <otaylor@gtk.org>              98/4/4 */

21
/*
22
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
23 24 25 26 27
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
 */

28
#include "config.h"
29

30
#include "gtksocketprivate.h"
31

32 33
#include <string.h>

34
#include "gtkmainprivate.h"
35
#include "gtkmarshalers.h"
36
#include "gtksizerequest.h"
37
#include "gtkwindowprivate.h"
38
#include "gtkplug.h"
39
#include "gtkprivate.h"
40
#include "gtkdnd.h"
41
#include "gtkdebug.h"
42
#include "gtkintl.h"
43
#include "gtkwidgetprivate.h"
44

45
#include <gdk/gdkx.h>
46 47 48 49 50 51 52
#include <gdk/gdkprivate.h>

#ifdef HAVE_XFIXES
#include <X11/extensions/Xfixes.h>
#endif

#include "gtkxembed.h"
53

54

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
/**
 * SECTION:gtksocket
 * @Short_description: Container for widgets from other processes
 * @Title: GtkSocket
 * @See_also: #GtkPlug, <ulink url="http://www.freedesktop.org/Standards/xembed-spec">XEmbed</ulink>
 *
 * Together with #GtkPlug, #GtkSocket provides the ability
 * to embed widgets from one process into another process
 * in a fashion that is transparent to the user. One
 * process creates a #GtkSocket widget and passes
 * that widget's window ID to the other process,
 * which then creates a #GtkPlug with that window ID.
 * Any widgets contained in the #GtkPlug then will appear
 * inside the first application's window.
 *
 * The socket's window ID is obtained by using
 * gtk_socket_get_id(). Before using this function,
 * the socket must have been realized, and for hence,
 * have been added to its parent.
 *
 * <example>
 * <title>Obtaining the window ID of a socket.</title>
 * <programlisting>
 * GtkWidget *socket = gtk_socket_new (<!-- -->);
 * gtk_widget_show (socket);
 * gtk_container_add (GTK_CONTAINER (parent), socket);
 *
 * /<!---->* The following call is only necessary if one of
 *  * the ancestors of the socket is not yet visible.
 *  *<!---->/
 * gtk_widget_realize (socket);
 * g_print ("The ID of the sockets window is %#x\n",
 *          gtk_socket_get_id (socket));
 * </programlisting>
 * </example>
 *
 * Note that if you pass the window ID of the socket to another
 * process that will create a plug in the socket, you
 * must make sure that the socket widget is not destroyed
 * until that plug is created. Violating this rule will
 * cause unpredictable consequences, the most likely
 * consequence being that the plug will appear as a
 * separate toplevel window. You can check if the plug
 * has been created by using gtk_socket_get_plug_window(). If
 * it returns a non-%NULL value, then the plug has been
 * successfully created inside of the socket.
 *
 * When GTK+ is notified that the embedded window has been
 * destroyed, then it will destroy the socket as well. You
 * should always, therefore, be prepared for your sockets
 * to be destroyed at any time when the main event loop
 * is running. To prevent this from happening, you can
 * connect to the #GtkSocket::plug-removed signal.
 *
 * The communication between a #GtkSocket and a #GtkPlug follows the
 * <ulink url="http://www.freedesktop.org/Standards/xembed-spec">XEmbed</ulink>
 * protocol. This protocol has also been implemented in other toolkits, e.g.
 * <application>Qt</application>, allowing the same level of integration
 * when embedding a <application>Qt</application> widget in GTK or vice versa.
 *
 * <note>
116 117 118
 * The #GtkPlug and #GtkSocket widgets are only available when GTK is
 * compiled for the X11 platform and %GDK_WINDOWING_X11 is defined.
 * They can only be used on a #GdkX11Display.
119 120 121
 * </note>
 */

122 123
/* Forward declararations */

124
static void     gtk_socket_finalize             (GObject          *object);
125 126
static void     gtk_socket_notify               (GObject          *object,
						 GParamSpec       *pspec);
127 128
static void     gtk_socket_realize              (GtkWidget        *widget);
static void     gtk_socket_unrealize            (GtkWidget        *widget);
129 130 131 132 133 134
static void     gtk_socket_get_preferred_width  (GtkWidget        *widget,
                                                 gint             *minimum,
                                                 gint             *natural);
static void     gtk_socket_get_preferred_height (GtkWidget        *widget,
                                                 gint             *minimum,
                                                 gint             *natural);
135 136 137 138 139 140
static void     gtk_socket_size_allocate        (GtkWidget        *widget,
						 GtkAllocation    *allocation);
static void     gtk_socket_hierarchy_changed    (GtkWidget        *widget,
						 GtkWidget        *old_toplevel);
static void     gtk_socket_grab_notify          (GtkWidget        *widget,
						 gboolean          was_grabbed);
141
static gboolean gtk_socket_key_event            (GtkWidget        *widget,
142 143 144 145 146 147 148 149 150
						 GdkEventKey      *event);
static gboolean gtk_socket_focus                (GtkWidget        *widget,
						 GtkDirectionType  direction);
static void     gtk_socket_remove               (GtkContainer     *container,
						 GtkWidget        *widget);
static void     gtk_socket_forall               (GtkContainer     *container,
						 gboolean          include_internals,
						 GtkCallback       callback,
						 gpointer          callback_data);
151
static void     gtk_socket_add_window           (GtkSocket        *socket,
152
                                                 Window            xid,
153 154 155 156 157 158 159 160 161 162 163
                                                 gboolean          need_reparent);
static GdkFilterReturn gtk_socket_filter_func   (GdkXEvent        *gdk_xevent,
                                                 GdkEvent         *event,
                                                 gpointer          data);

static gboolean xembed_get_info                 (GdkWindow        *gdk_window,
                                                 unsigned long    *version,
                                                 unsigned long    *flags);

/* From Tk */
#define EMBEDDED_APP_WANTS_FOCUS NotifyNormal+20
164 165


166 167
/* Local data */

168 169 170 171 172 173
typedef struct
{
  guint			 accel_key;
  GdkModifierType	 accel_mods;
} GrabbedKey;

174 175 176 177 178 179 180 181
enum {
  PLUG_ADDED,
  PLUG_REMOVED,
  LAST_SIGNAL
}; 

static guint socket_signals[LAST_SIGNAL] = { 0 };

Matthias Clasen's avatar
Matthias Clasen committed
182
G_DEFINE_TYPE (GtkSocket, gtk_socket, GTK_TYPE_CONTAINER)
183

184 185 186 187
static void
gtk_socket_finalize (GObject *object)
{
  GtkSocket *socket = GTK_SOCKET (object);
188 189 190
  GtkSocketPrivate *priv = socket->priv;

  g_object_unref (priv->accel_group);
191

Matthias Clasen's avatar
Matthias Clasen committed
192
  G_OBJECT_CLASS (gtk_socket_parent_class)->finalize (object);
193 194
}

195 196 197 198 199
static void
gtk_socket_class_init (GtkSocketClass *class)
{
  GtkWidgetClass *widget_class;
  GtkContainerClass *container_class;
200
  GObjectClass *gobject_class;
201

202
  gobject_class = (GObjectClass *) class;
203 204 205
  widget_class = (GtkWidgetClass*) class;
  container_class = (GtkContainerClass*) class;

206
  gobject_class->finalize = gtk_socket_finalize;
207
  gobject_class->notify = gtk_socket_notify;
208

209 210
  widget_class->realize = gtk_socket_realize;
  widget_class->unrealize = gtk_socket_unrealize;
211 212
  widget_class->get_preferred_width = gtk_socket_get_preferred_width;
  widget_class->get_preferred_height = gtk_socket_get_preferred_height;
213
  widget_class->size_allocate = gtk_socket_size_allocate;
214 215
  widget_class->hierarchy_changed = gtk_socket_hierarchy_changed;
  widget_class->grab_notify = gtk_socket_grab_notify;
216 217
  widget_class->key_press_event = gtk_socket_key_event;
  widget_class->key_release_event = gtk_socket_key_event;
218
  widget_class->focus = gtk_socket_focus;
219

220
  /* We don't want to show_all the in-process plug, if any.
221 222
   */
  widget_class->show_all = gtk_widget_show;
223

224 225
  container_class->remove = gtk_socket_remove;
  container_class->forall = gtk_socket_forall;
226

Matthias Clasen's avatar
Matthias Clasen committed
227 228 229 230 231 232 233
  /**
   * GtkSocket::plug-added:
   * @socket_: the object which received the signal
   *
   * This signal is emitted when a client is successfully
   * added to the socket. 
   */
234
  socket_signals[PLUG_ADDED] =
235
    g_signal_new (I_("plug-added"),
236 237 238 239
		  G_OBJECT_CLASS_TYPE (class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkSocketClass, plug_added),
		  NULL, NULL,
240
		  _gtk_marshal_VOID__VOID,
Manish Singh's avatar
Manish Singh committed
241
		  G_TYPE_NONE, 0);
Matthias Clasen's avatar
Matthias Clasen committed
242 243 244 245 246 247 248 249 250 251 252

  /**
   * GtkSocket::plug-removed:
   * @socket_: the object which received the signal
   *
   * This signal is emitted when a client is removed from the socket. 
   * The default action is to destroy the #GtkSocket widget, so if you 
   * want to reuse it you must add a signal handler that returns %TRUE. 
   *
   * Return value: %TRUE to stop other handlers from being invoked.
   */
253
  socket_signals[PLUG_REMOVED] =
254
    g_signal_new (I_("plug-removed"),
255 256 257 258
		  G_OBJECT_CLASS_TYPE (class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkSocketClass, plug_removed),
                  _gtk_boolean_handled_accumulator, NULL,
259
		  _gtk_marshal_BOOLEAN__VOID,
260
		  G_TYPE_BOOLEAN, 0);
261 262

  g_type_class_add_private (gobject_class, sizeof (GtkSocketPrivate));
263 264 265 266 267
}

static void
gtk_socket_init (GtkSocket *socket)
{
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
  GtkSocketPrivate *priv;

  priv = G_TYPE_INSTANCE_GET_PRIVATE (socket,
                                      GTK_TYPE_SOCKET,
                                      GtkSocketPrivate);
  socket->priv = priv;
  priv->request_width = 0;
  priv->request_height = 0;
  priv->current_width = 0;
  priv->current_height = 0;

  priv->plug_window = NULL;
  priv->plug_widget = NULL;
  priv->focus_in = FALSE;
  priv->have_size = FALSE;
  priv->need_map = FALSE;
  priv->active = FALSE;

  priv->accel_group = gtk_accel_group_new ();
  g_object_set_data (G_OBJECT (priv->accel_group), I_("gtk-socket"), socket);
288 289
}

290 291 292 293 294 295 296
/**
 * gtk_socket_new:
 * 
 * Create a new empty #GtkSocket.
 * 
 * Return value:  the new #GtkSocket.
 **/
297
GtkWidget*
Tim Janik's avatar
Tim Janik committed
298
gtk_socket_new (void)
299 300 301
{
  GtkSocket *socket;

302
  socket = g_object_new (GTK_TYPE_SOCKET, NULL);
303 304 305 306

  return GTK_WIDGET (socket);
}

307 308
/**
 * gtk_socket_add_id:
309
 * @socket_: a #GtkSocket
310
 * @window: the Window of a client participating in the XEMBED protocol.
311 312 313 314 315
 *
 * Adds an XEMBED client, such as a #GtkPlug, to the #GtkSocket.  The
 * client may be in the same process or in a different process. 
 * 
 * To embed a #GtkPlug in a #GtkSocket, you can either create the
316 317
 * #GtkPlug with <literal>gtk_plug_new (0)</literal>, call 
 * gtk_plug_get_id() to get the window ID of the plug, and then pass that to the
318 319 320 321 322 323 324 325
 * gtk_socket_add_id(), or you can call gtk_socket_get_id() to get the
 * window ID for the socket, and call gtk_plug_new() passing in that
 * ID.
 *
 * The #GtkSocket must have already be added into a toplevel window
 *  before you can make this call.
 **/
void           
326
gtk_socket_add_id (GtkSocket      *socket,
327
		   Window          window)
328 329
{
  g_return_if_fail (GTK_IS_SOCKET (socket));
330
  g_return_if_fail (_gtk_widget_get_anchored (GTK_WIDGET (socket)));
331

332
  if (!gtk_widget_get_realized (GTK_WIDGET (socket)))
333 334
    gtk_widget_realize (GTK_WIDGET (socket));

335
  gtk_socket_add_window (socket, window, TRUE);
336 337 338 339
}

/**
 * gtk_socket_get_id:
340
 * @socket_: a #GtkSocket.
341 342 343
 * 
 * Gets the window ID of a #GtkSocket widget, which can then
 * be used to create a client embedded inside the socket, for
344
 * instance with gtk_plug_new(). 
Matthias Clasen's avatar
Matthias Clasen committed
345 346 347
 *
 * The #GtkSocket must have already be added into a toplevel window 
 * before you can make this call.
348 349 350
 * 
 * Return value: the window ID for the socket
 **/
351
Window
352 353 354
gtk_socket_get_id (GtkSocket *socket)
{
  g_return_val_if_fail (GTK_IS_SOCKET (socket), 0);
355
  g_return_val_if_fail (_gtk_widget_get_anchored (GTK_WIDGET (socket)), 0);
356

357
  if (!gtk_widget_get_realized (GTK_WIDGET (socket)))
358 359
    gtk_widget_realize (GTK_WIDGET (socket));

360
  return GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (socket)));
361 362
}

Tim Janik's avatar
Tim Janik committed
363 364 365 366 367 368 369
/**
 * gtk_socket_get_plug_window:
 * @socket_: a #GtkSocket.
 *
 * Retrieves the window of the plug. Use this to check if the plug has
 * been created inside of the socket.
 *
370
 * Return value: (transfer none): the window of the plug if available, or %NULL
Tim Janik's avatar
Tim Janik committed
371
 *
372
 * Since:  2.14
Tim Janik's avatar
Tim Janik committed
373 374 375 376 377 378
 **/
GdkWindow*
gtk_socket_get_plug_window (GtkSocket *socket)
{
  g_return_val_if_fail (GTK_IS_SOCKET (socket), NULL);

379
  return socket->priv->plug_window;
Tim Janik's avatar
Tim Janik committed
380 381
}

382 383 384
static void
gtk_socket_realize (GtkWidget *widget)
{
385
  GtkAllocation allocation;
386
  GtkSocket *socket = GTK_SOCKET (widget);
387
  GdkWindow *window;
388
  GdkWindowAttr attributes;
389
  XWindowAttributes xattrs;
390 391
  gint attributes_mask;

392
  gtk_widget_set_realized (widget, TRUE);
393

394 395
  gtk_widget_get_allocation (widget, &allocation);

396
  attributes.window_type = GDK_WINDOW_CHILD;
397 398 399 400
  attributes.x = allocation.x;
  attributes.y = allocation.y;
  attributes.width = allocation.width;
  attributes.height = allocation.height;
401 402 403 404
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.event_mask = GDK_FOCUS_CHANGE_MASK;

405
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
406

407 408 409 410
  window = gdk_window_new (gtk_widget_get_parent_window (widget),
                           &attributes, attributes_mask);
  gtk_widget_set_window (widget, window);
  gdk_window_set_user_data (window, socket);
411

412 413
  gtk_style_context_set_background (gtk_widget_get_style_context (widget),
                                    window);
414

415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
  XGetWindowAttributes (GDK_WINDOW_XDISPLAY (window),
			GDK_WINDOW_XID (window),
			&xattrs);

  /* Sooooo, it turns out that mozilla, as per the gtk2xt code selects
     for input on the socket with a mask of 0x0fffff (for god knows why)
     which includes ButtonPressMask causing a BadAccess if someone else
     also selects for this. As per the client-side windows merge we always
     normally selects for button press so we can emulate it on client
     side children that selects for button press. However, we don't need
     this for GtkSocket, so we unselect it here, fixing the crashes in
     firefox. */
  XSelectInput (GDK_WINDOW_XDISPLAY (window),
		GDK_WINDOW_XID (window), 
		(xattrs.your_event_mask & ~ButtonPressMask) |
		SubstructureNotifyMask | SubstructureRedirectMask);
431

432
  gdk_window_add_filter (window,
433
			 gtk_socket_filter_func,
434
			 widget);
435 436 437 438 439

  /* We sync here so that we make sure that if the XID for
   * our window is passed to another application, SubstructureRedirectMask
   * will be set by the time the other app creates its window.
   */
440
  gdk_display_sync (gtk_widget_get_display (widget));
441 442
}

443
/**
444
 * gtk_socket_end_embedding:
445 446 447 448 449
 *
 * @socket: a #GtkSocket
 *
 * Called to end the embedding of a plug in the socket.
 */
450 451
static void
gtk_socket_end_embedding (GtkSocket *socket)
452
{
453
  GtkSocketPrivate *private = socket->priv;
454
  GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (socket));
455

456
  if (GTK_IS_WINDOW (toplevel))
457
    _gtk_window_remove_embedded_xid (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (socket))),
458
                                    GDK_WINDOW_XID (socket->priv->plug_window));
459

460 461 462 463
  g_object_unref (private->plug_window);
  private->plug_window = NULL;
  private->current_width = 0;
  private->current_height = 0;
464
  private->resize_count = 0;
465

466
  gtk_accel_group_disconnect (private->accel_group, NULL);
467 468
}

469 470 471
static void
gtk_socket_unrealize (GtkWidget *widget)
{
472
  GtkSocket *socket = GTK_SOCKET (widget);
473
  GtkSocketPrivate *private = socket->priv;
474

475
  gtk_widget_set_realized (widget, FALSE);
476

477
  if (private->plug_widget)
478
    {
479
      _gtk_plug_remove_from_socket (GTK_PLUG (private->plug_widget), socket);
480
    }
481
  else if (private->plug_window)
482
    {
483
      gtk_socket_end_embedding (socket);
484 485
    }

486
  GTK_WIDGET_CLASS (gtk_socket_parent_class)->unrealize (widget);
487
}
488

489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
static void
gtk_socket_size_request (GtkSocket *socket)
{
  GtkSocketPrivate *private = socket->priv;
  XSizeHints hints;
  long supplied;
	  
  gdk_error_trap_push ();

  private->request_width = 1;
  private->request_height = 1;
	  
  if (XGetWMNormalHints (GDK_WINDOW_XDISPLAY (private->plug_window),
			 GDK_WINDOW_XID (private->plug_window),
			 &hints, &supplied))
    {
      if (hints.flags & PMinSize)
	{
	  private->request_width = MAX (hints.min_width, 1);
	  private->request_height = MAX (hints.min_height, 1);
	}
      else if (hints.flags & PBaseSize)
	{
	  private->request_width = MAX (hints.base_width, 1);
	  private->request_height = MAX (hints.base_height, 1);
	}
    }
  private->have_size = TRUE;
  
  gdk_error_trap_pop_ignored ();
}

521
static void
522 523 524
gtk_socket_get_preferred_width (GtkWidget *widget,
                                gint      *minimum,
                                gint      *natural)
525
{
526
  GtkSocket *socket = GTK_SOCKET (widget);
527
  GtkSocketPrivate *private = socket->priv;
528

529
  if (private->plug_widget)
530
    {
531
      gtk_widget_get_preferred_width (private->plug_widget, minimum, natural);
532 533 534
    }
  else
    {
535
      if (private->is_mapped && !private->have_size && private->plug_window)
536
        gtk_socket_size_request (socket);
537

538 539
      if (private->is_mapped && private->have_size)
        *minimum = *natural = MAX (private->request_width, 1);
540
      else
541 542 543 544 545 546 547 548 549 550
        *minimum = *natural = 1;
    }
}

static void
gtk_socket_get_preferred_height (GtkWidget *widget,
                                 gint      *minimum,
                                 gint      *natural)
{
  GtkSocket *socket = GTK_SOCKET (widget);
551
  GtkSocketPrivate *private = socket->priv;
552

553
  if (private->plug_widget)
554
    {
555
      gtk_widget_get_preferred_height (private->plug_widget, minimum, natural);
556 557 558
    }
  else
    {
559
      if (private->is_mapped && !private->have_size && private->plug_window)
560
        gtk_socket_size_request (socket);
561

562 563
      if (private->is_mapped && private->have_size)
        *minimum = *natural = MAX (private->request_height, 1);
564 565
      else
        *minimum = *natural = 1;
566 567 568
    }
}

569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
static void
gtk_socket_send_configure_event (GtkSocket *socket)
{
  GtkAllocation allocation;
  XConfigureEvent xconfigure;
  gint x, y;

  g_return_if_fail (socket->priv->plug_window != NULL);

  memset (&xconfigure, 0, sizeof (xconfigure));
  xconfigure.type = ConfigureNotify;

  xconfigure.event = GDK_WINDOW_XID (socket->priv->plug_window);
  xconfigure.window = GDK_WINDOW_XID (socket->priv->plug_window);

  /* The ICCCM says that synthetic events should have root relative
   * coordinates. We still aren't really ICCCM compliant, since
   * we don't send events when the real toplevel is moved.
   */
  gdk_error_trap_push ();
  gdk_window_get_origin (socket->priv->plug_window, &x, &y);
  gdk_error_trap_pop_ignored ();

  gtk_widget_get_allocation (GTK_WIDGET(socket), &allocation);
  xconfigure.x = x;
  xconfigure.y = y;
  xconfigure.width = allocation.width;
  xconfigure.height = allocation.height;

  xconfigure.border_width = 0;
  xconfigure.above = None;
  xconfigure.override_redirect = False;

  gdk_error_trap_push ();
  XSendEvent (GDK_WINDOW_XDISPLAY (socket->priv->plug_window),
	      GDK_WINDOW_XID (socket->priv->plug_window),
	      False, NoEventMask, (XEvent *)&xconfigure);
  gdk_error_trap_pop_ignored ();
}

609 610 611 612
static void
gtk_socket_size_allocate (GtkWidget     *widget,
			  GtkAllocation *allocation)
{
613
  GtkSocket *socket = GTK_SOCKET (widget);
614
  GtkSocketPrivate *private = socket->priv;
615

616
  gtk_widget_set_allocation (widget, allocation);
617
  if (gtk_widget_get_realized (widget))
618
    {
619
      gdk_window_move_resize (gtk_widget_get_window (widget),
620 621 622
			      allocation->x, allocation->y,
			      allocation->width, allocation->height);

623
      if (private->plug_widget)
624 625 626 627 628 629 630 631
	{
	  GtkAllocation child_allocation;

	  child_allocation.x = 0;
	  child_allocation.y = 0;
	  child_allocation.width = allocation->width;
	  child_allocation.height = allocation->height;

632
	  gtk_widget_size_allocate (private->plug_widget, &child_allocation);
633
	}
634
      else if (private->plug_window)
635
	{
636
	  gdk_error_trap_push ();
637 638 639

	  if (allocation->width != private->current_width ||
	      allocation->height != private->current_height)
640
	    {
641
	      gdk_window_move_resize (private->plug_window,
642 643
				      0, 0,
				      allocation->width, allocation->height);
644 645 646
	      if (private->resize_count)
		private->resize_count--;
	      
647 648 649
	      GTK_NOTE (PLUGSOCKET,
			g_message ("GtkSocket - allocated: %d %d",
				   allocation->width, allocation->height));
650 651
	      private->current_width = allocation->width;
	      private->current_height = allocation->height;
652 653
	    }

654
	  if (private->need_map)
655
	    {
656 657
	      gdk_window_show (private->plug_window);
	      private->need_map = FALSE;
658 659
	    }

660 661
	  while (private->resize_count)
 	    {
662
 	      gtk_socket_send_configure_event (socket);
663
 	      private->resize_count--;
664 665 666
 	      GTK_NOTE (PLUGSOCKET,
			g_message ("GtkSocket - sending synthetic configure: %d %d",
				   allocation->width, allocation->height));
667
 	    }
668

669
	  gdk_error_trap_pop_ignored ();
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
static void
gtk_socket_send_key_event (GtkSocket *socket,
			   GdkEvent  *gdk_event,
			   gboolean   mask_key_presses)
{
  XKeyEvent xkey;
  GdkScreen *screen = gdk_window_get_screen (socket->priv->plug_window);

  memset (&xkey, 0, sizeof (xkey));
  xkey.type = (gdk_event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
  xkey.window = GDK_WINDOW_XID (socket->priv->plug_window);
  xkey.root = GDK_WINDOW_XID (gdk_screen_get_root_window (screen));
  xkey.subwindow = None;
  xkey.time = gdk_event->key.time;
  xkey.x = 0;
  xkey.y = 0;
  xkey.x_root = 0;
  xkey.y_root = 0;
  xkey.state = gdk_event->key.state;
  xkey.keycode = gdk_event->key.hardware_keycode;
  xkey.same_screen = True;/* FIXME ? */

  gdk_error_trap_push ();
  XSendEvent (GDK_WINDOW_XDISPLAY (socket->priv->plug_window),
	      GDK_WINDOW_XID (socket->priv->plug_window),
	      False,
	      (mask_key_presses ? KeyPressMask : NoEventMask),
	      (XEvent *)&xkey);
  gdk_error_trap_pop_ignored ();
}

705 706 707 708 709 710
static gboolean
activate_key (GtkAccelGroup  *accel_group,
	      GObject        *acceleratable,
	      guint           accel_key,
	      GdkModifierType accel_mods,
	      GrabbedKey     *grabbed_key)
711
{
712
  GdkEvent *gdk_event = gtk_get_current_event ();
713
  
714
  GtkSocket *socket = g_object_get_data (G_OBJECT (accel_group), "gtk-socket");
715
  gboolean retval = FALSE;
716

717
  if (gdk_event && gdk_event->type == GDK_KEY_PRESS && socket->priv->plug_window)
718
    {
719
      gtk_socket_send_key_event (socket, gdk_event, FALSE);
720
      retval = TRUE;
721 722 723 724
    }

  if (gdk_event)
    gdk_event_free (gdk_event);
725 726

  return retval;
727 728 729
}

static gboolean
730 731 732
find_accel_key (GtkAccelKey *key,
		GClosure    *closure,
		gpointer     data)
733
{
734 735 736 737
  GrabbedKey *grabbed_key = data;
  
  return (key->accel_key == grabbed_key->accel_key &&
	  key->accel_mods == grabbed_key->accel_mods);
738 739
}

740
/**
741
 * gtk_socket_add_grabbed_key:
742 743 744 745 746 747 748 749
 *
 * @socket: a #GtkSocket
 * @keyval: a key
 * @modifiers: modifiers for the key
 *
 * Called from the GtkSocket platform-specific backend when the
 * corresponding plug has told the socket to grab a key.
 */
750 751 752 753
static void
gtk_socket_add_grabbed_key (GtkSocket       *socket,
			    guint            keyval,
			    GdkModifierType  modifiers)
754
{
755 756
  GClosure *closure;
  GrabbedKey *grabbed_key;
757

758 759 760 761
  grabbed_key = g_new (GrabbedKey, 1);
  
  grabbed_key->accel_key = keyval;
  grabbed_key->accel_mods = modifiers;
762

763
  if (gtk_accel_group_find (socket->priv->accel_group,
764 765 766 767 768 769 770
			    find_accel_key,
			    &grabbed_key))
    {
      g_warning ("GtkSocket: request to add already present grabbed key %u,%#x\n",
		 keyval, modifiers);
      g_free (grabbed_key);
      return;
771 772
    }

773
  closure = g_cclosure_new (G_CALLBACK (activate_key), grabbed_key, (GClosureNotify)g_free);
774

775
  gtk_accel_group_connect (socket->priv->accel_group, keyval, modifiers, GTK_ACCEL_LOCKED,
776
			   closure);
777 778
}

779
/**
780
 * gtk_socket_remove_grabbed_key:
781 782 783 784 785 786 787 788
 *
 * @socket: a #GtkSocket
 * @keyval: a key
 * @modifiers: modifiers for the key
 *
 * Called from the GtkSocket backend when the corresponding plug has
 * told the socket to remove a key grab.
 */
789 790 791 792
static void
gtk_socket_remove_grabbed_key (GtkSocket      *socket,
			       guint           keyval,
			       GdkModifierType modifiers)
793
{
794
  if (!gtk_accel_group_disconnect_key (socket->priv->accel_group, keyval, modifiers))
795 796
    g_warning ("GtkSocket: request to remove non-present grabbed key %u,%#x\n",
	       keyval, modifiers);
797 798
}

799 800
static void
socket_update_focus_in (GtkSocket *socket)
801
{
802
  GtkSocketPrivate *private = socket->priv;
803 804
  gboolean focus_in = FALSE;

805
  if (private->plug_window)
806 807
    {
      GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (socket));
808

809
      if (gtk_widget_is_toplevel (toplevel) &&
810
	  gtk_window_has_toplevel_focus (GTK_WINDOW (toplevel)) &&
811 812 813 814
	  gtk_widget_is_focus (GTK_WIDGET (socket)))
	focus_in = TRUE;
    }

815
  if (focus_in != private->focus_in)
816
    {
817
      private->focus_in = focus_in;
818

819 820 821 822 823 824
      if (focus_in)
        _gtk_xembed_send_focus_message (private->plug_window,
                                        XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT);
      else
        _gtk_xembed_send_message (private->plug_window,
                                  XEMBED_FOCUS_OUT, 0, 0, 0);
825
    }
826 827
}

828 829
static void
socket_update_active (GtkSocket *socket)
830
{
831
  GtkSocketPrivate *private = socket->priv;
832
  gboolean active = FALSE;
833

834
  if (private->plug_window)
835 836
    {
      GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (socket));
837

838
      if (gtk_widget_is_toplevel (toplevel) &&
839
	  gtk_window_is_active  (GTK_WINDOW (toplevel)))
840 841 842
	active = TRUE;
    }

843
  if (active != private->active)
844
    {
845
      private->active = active;
846

847 848 849
      _gtk_xembed_send_message (private->plug_window,
                                active ? XEMBED_WINDOW_ACTIVATE : XEMBED_WINDOW_DEACTIVATE,
                                0, 0, 0);
850
    }
851 852 853
}

static void
854 855
gtk_socket_hierarchy_changed (GtkWidget *widget,
			      GtkWidget *old_toplevel)
856 857
{
  GtkSocket *socket = GTK_SOCKET (widget);
858
  GtkSocketPrivate *private = socket->priv;
859 860 861 862 863
  GtkWidget *toplevel = gtk_widget_get_toplevel (widget);

  if (toplevel && !GTK_IS_WINDOW (toplevel))
    toplevel = NULL;

864
  if (toplevel != private->toplevel)
865
    {
866
      if (private->toplevel)
867
	{
868 869
	  gtk_window_remove_accel_group (GTK_WINDOW (private->toplevel), private->accel_group);
	  g_signal_handlers_disconnect_by_func (private->toplevel,
Manish Singh's avatar
Manish Singh committed
870 871
						socket_update_focus_in,
						socket);
872
	  g_signal_handlers_disconnect_by_func (private->toplevel,
Manish Singh's avatar
Manish Singh committed
873 874
						socket_update_active,
						socket);
875 876
	}

877
      private->toplevel = toplevel;
878 879 880

      if (toplevel)
	{
881 882
	  gtk_window_add_accel_group (GTK_WINDOW (private->toplevel), private->accel_group);
	  g_signal_connect_swapped (private->toplevel, "notify::has-toplevel-focus",
883
				    G_CALLBACK (socket_update_focus_in), socket);
884
	  g_signal_connect_swapped (private->toplevel, "notify::is-active",
885
				    G_CALLBACK (socket_update_active), socket);
886
	}
887 888 889

      socket_update_focus_in (socket);
      socket_update_active (socket);
890 891 892 893 894 895 896
    }
}

static void
gtk_socket_grab_notify (GtkWidget *widget,
			gboolean   was_grabbed)
{
897 898
  GtkSocket *socket = GTK_SOCKET (widget);

899
  if (!socket->priv->same_app)
900 901 902
    _gtk_xembed_send_message (socket->priv->plug_window,
                              was_grabbed ? XEMBED_MODALITY_OFF : XEMBED_MODALITY_ON,
                              0, 0, 0);
903 904 905
}

static gboolean
906 907
gtk_socket_key_event (GtkWidget   *widget,
                      GdkEventKey *event)
908 909
{
  GtkSocket *socket = GTK_SOCKET (widget);
910
  GtkSocketPrivate *private = socket->priv;
911
  
912
  if (gtk_widget_has_focus (widget) && private->plug_window && !private->plug_widget)
913
    {
914
      gtk_socket_send_key_event (socket, (GdkEvent *) event, FALSE);
915

916 917 918 919 920 921
      return TRUE;
    }
  else
    return FALSE;
}

922 923 924
static void
gtk_socket_notify (GObject    *object,
		   GParamSpec *pspec)
925
{
926
  if (!strcmp (pspec->name, "is-focus"))
927 928
    return;
  socket_update_focus_in (GTK_SOCKET (object));
929 930
}

931
/**
932
 * gtk_socket_claim_focus:
933 934 935 936 937 938
 *
 * @socket: a #GtkSocket
 * @send_event: huh?
 *
 * Claims focus for the socket. XXX send_event?
 */
939 940 941
static void
gtk_socket_claim_focus (GtkSocket *socket,
			gboolean   send_event)
942
{
943
  GtkWidget *widget = GTK_WIDGET (socket);
944
  GtkSocketPrivate *private = socket->priv;
945

946
  if (!send_event)
947
    private->focus_in = TRUE;	/* Otherwise, our notify handler will send FOCUS_IN  */
948 949 950
      
  /* Oh, the trickery... */
  
951 952 953
  gtk_widget_set_can_focus (widget, TRUE);
  gtk_widget_grab_focus (widget);
  gtk_widget_set_can_focus (widget, FALSE);
954 955
}

956
static gboolean
957 958
gtk_socket_focus (GtkWidget       *widget,
		  GtkDirectionType direction)
959
{
960
  GtkSocket *socket = GTK_SOCKET (widget);
961
  GtkSocketPrivate *private = socket->priv;
962

963 964
  if (private->plug_widget)
    return gtk_widget_child_focus (private->plug_widget, direction);
965

966
  if (!gtk_widget_is_focus (widget))
967
    {
968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985
      gint detail = -1;

      switch (direction)
        {
        case GTK_DIR_UP:
        case GTK_DIR_LEFT:
        case GTK_DIR_TAB_BACKWARD:
          detail = XEMBED_FOCUS_LAST;
          break;
        case GTK_DIR_DOWN:
        case GTK_DIR_RIGHT:
        case GTK_DIR_TAB_FORWARD:
          detail = XEMBED_FOCUS_FIRST;
          break;
        }
      
      _gtk_xembed_send_focus_message (private->plug_window, XEMBED_FOCUS_IN, detail);
      gtk_socket_claim_focus (socket, FALSE);
986 987 988 989 990
 
      return TRUE;
    }
  else
    return FALSE;
991 992
}

993 994 995 996 997
static void
gtk_socket_remove (GtkContainer *container,
		   GtkWidget    *child)
{
  GtkSocket *socket = GTK_SOCKET (container);
998
  GtkSocketPrivate *private = socket->priv;
999

1000
  g_return_if_fail (child == private->plug_widget);
1001

1002
  _gtk_plug_remove_from_socket (GTK_PLUG (private->plug_widget), socket);
1003 1004 1005 1006 1007 1008 1009 1010 1011
}

static void
gtk_socket_forall (GtkContainer *container,
		   gboolean      include_internals,
		   GtkCallback   callback,
		   gpointer      callback_data)
{
  GtkSocket *socket = GTK_SOCKET (container);
1012
  GtkSocketPrivate *private = socket->priv;
1013

1014 1015
  if (private->plug_widget)
    (* callback) (private->plug_widget, callback_data);
1016 1017
}

1018
/**
1019
 * gtk_socket_add_window:
1020 1021 1022 1023 1024 1025 1026 1027
 *
 * @socket: a #GtkSocket
 * @xid: the native identifier for a window
 * @need_reparent: whether the socket's plug's window needs to be
 *		   reparented to the socket
 *
 * Adds a window to a GtkSocket.
 */
1028 1029
static void
gtk_socket_add_window (GtkSocket       *socket,
1030
		       Window           xid,
1031
		       gboolean         need_reparent)
1032
{
1033
  GtkWidget *widget = GTK_WIDGET (socket);
1034
  GdkDisplay *display = gtk_widget_get_display (widget);
1035
  gpointer user_data = NULL;
1036
  GtkSocketPrivate *private = socket->priv;
1037 1038
  unsigned long version;
  unsigned long flags;
1039

1040
  if (GDK_IS_X11_DISPLAY (display))
1041 1042 1043
    private->plug_window = gdk_x11_window_lookup_for_display (display, xid);
  else
    private->plug_window = NULL;
1044

1045
  if (private->plug_window)
1046
    {
1047 1048
      g_object_ref (private->plug_window);
      gdk_window_get_user_data (private->plug_window, &user_data);
1049 1050
    }

1051
  if (user_data) /* A widget's window in this process */
1052 1053 1054 1055
    {
      GtkWidget *child_widget = user_data;

      if (!GTK_IS_PLUG (child_widget))
1056 1057 1058 1059 1060 1061 1062
        {
          g_warning (G_STRLOC ": Can't add non-GtkPlug to GtkSocket");
          private->plug_window = NULL;
          gdk_error_trap_pop_ignored ();

          return;
        }
1063 1064 1065

      _gtk_plug_add_to_socket (GTK_PLUG (child_widget), socket);
    }
1066
  else  /* A foreign window */
1067 1068 1069
    {
      GtkWidget *toplevel;
      GdkDragProtocol protocol;
1070 1071

      gdk_error_trap_push ();
1072

1073
      if (!private->plug_window)
1074
        {
1075
          if (GDK_IS_X11_DISPLAY (display))
1076 1077 1078 1079 1080 1081 1082 1083
            private->plug_window = gdk_x11_window_foreign_new_for_display (display, xid);
          if (!private->plug_window) /* was deleted before we could get it */
            {
              gdk_error_trap_pop_ignored ();
              return;
            }
        }

1084 1085 1086
      XSelectInput (GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (socket))),
                    GDK_WINDOW_XID (private->plug_window),
                    StructureNotifyMask | PropertyChangeMask);
1087

1088 1089
      if (gdk_error_trap_pop ())
	{
1090 1091
	  g_object_unref (private->plug_window);
	  private->plug_window = NULL;
1092 1093 1094 1095 1096 1097 1098 1099 1100
	  return;
	}
      
      /* OK, we now will reliably get destroy notification on socket->plug_window */

      gdk_error_trap_push ();

      if (need_reparent)
	{
1101 1102
	  gdk_window_hide (private->plug_window); /* Shouldn't actually be necessary for XEMBED, but just in case */
	  gdk_window_reparent (private->plug_window,
1103 1104
                               gtk_widget_get_window (widget),
                               0, 0);
1105 1106
	}

1107
      private->have_size = FALSE;
1108

1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119
      private->xembed_version = -1;
      if (xembed_get_info (private->plug_window, &version, &flags))
        {
          private->xembed_version = MIN (GTK_XEMBED_PROTOCOL_VERSION, version);
          private->is_mapped = (flags & XEMBED_MAPPED) != 0;
        }
      else
        {
          /* FIXME, we should probably actually check the state before we started */
          private->is_mapped = TRUE;
        }
1120

1121
      private->need_map = private->is_mapped;
1122

1123
      if (gdk_drag_get_protocol_for_display (display, xid, &protocol))
1124
	gtk_drag_dest_set_proxy (GTK_WIDGET (socket), private->plug_window,
1125
				 protocol, TRUE);
1126

1127
      gdk_error_trap_pop_ignored ();
1128

1129
      gdk_window_add_filter (private->plug_window,
1130
			     gtk_socket_filter_func,
1131
			     socket);
1132 1133 1134 1135

      /* Add a pointer to the socket on our toplevel window */

      toplevel = gtk_widget_get_toplevel (GTK_WIDGET (socket));
1136
      if (GTK_IS_WINDOW (toplevel))
1137
	_gtk_window_add_embedded_xid (GTK_WINDOW (toplevel), xid);
1138

1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149
#ifdef HAVE_XFIXES
      gdk_error_trap_push ();
      XFixesChangeSaveSet (GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (socket))),
                           GDK_WINDOW_XID (private->plug_window),
                           SetModeInsert, SaveSetRoot, SaveSetUnmap);
      gdk_error_trap_pop_ignored ();
#endif
      _gtk_xembed_send_message (private->plug_window,
                                XEMBED_EMBEDDED_NOTIFY, 0,
                                GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (socket))),
                                private->xembed_version);
1150

1151 1152 1153
      socket_update_active (socket);
      socket_update_focus_in (socket);

1154 1155
      gtk_widget_queue_resize (GTK_WIDGET (socket));
    }
1156

1157
  if (private->plug_window)
Manish Singh's avatar
Manish Singh committed
1158
    g_signal_emit (socket, socket_signals[PLUG_ADDED], 0);
1159 1160
}

1161
/**
1162
 * gtk_socket_handle_map_request:
1163 1164 1165 1166 1167
 *
 * @socket: a #GtkSocket
 *
 * Called from the GtkSocket backend when the plug has been mapped.
 */
1168 1169
static void
gtk_socket_handle_map_request (GtkSocket *socket)
1170
{
1171 1172
  GtkSocketPrivate *private = socket->priv;
  if (!private->is_mapped)
1173
    {
1174 1175
      private->is_mapped = TRUE;
      private->need_map = TRUE;
1176 1177

      gtk_widget_queue_resize (GTK_WIDGET (socket));
1178
    }
1179 1180 1181
}

/**
1182
 * gtk_socket_unmap_notify:
1183 1184 1185 1186 1187
 *
 * @socket: a #GtkSocket
 *
 * Called from the GtkSocket backend when the plug has been unmapped ???
 */
1188 1189
static void
gtk_socket_unmap_notify (GtkSocket *socket)
1190
{
1191 1192
  GtkSocketPrivate *private = socket->priv;
  if (private->is_mapped)
1193
    {
1194
      private->is_mapped = FALSE;
1195
      gtk_widget_queue_resize (GTK_WIDGET (socket));
1196 1197 1198
    }
}

1199
/**
1200
 * gtk_socket_advance_toplevel_focus:
1201 1202 1203 1204 1205 1206 1207
 *
 * @socket: a #GtkSocket
 * @direction: a direction
 *
 * Called from the GtkSocket backend when the corresponding plug
 * has told the socket to move the focus.
 */
1208 1209 1210
static void
gtk_socket_advance_toplevel_focus (GtkSocket        *socket,
				   GtkDirectionType  direction)
1211 1212 1213 1214
{
  GtkBin *bin;
  GtkWindow *window;
  GtkContainer *container;
Javier Jardón's avatar
Javier Jardón committed
1215
  GtkWidget *child;
1216
  GtkWidget *focus_widget;
1217 1218 1219 1220 1221 1222 1223 1224
  GtkWidget *toplevel;
  GtkWidget *old_focus_child;
  GtkWidget *parent;

  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (socket));
  if (!toplevel)
    return;

1225
  if (!gtk_widget_is_toplevel (toplevel) || GTK_IS_PLUG (toplevel))
1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237
    {
      gtk_widget_child_focus (toplevel,direction);
      return;
    }

  container = GTK_CONTAINER (toplevel);
  window = GTK_WINDOW (toplevel);
  bin = GTK_BIN (toplevel);

  /* This is a copy of gtk_window_focus(), modified so that we
   * can detect wrap-around.
   */
1238
  old_focus_child = gtk_container_get_focus_child (container);
1239 1240 1241 1242 1243 1244 1245 1246 1247
  
  if (old_focus_child)
    {
      if (gtk_widget_child_focus (old_focus_child, direction))
	return;

      /* We are allowed exactly one wrap-around per sequence of focus
       * events
       */
1248
      if (_gtk_xembed_get_focus_wrapped ())
1249 1250
	return;
      else
1251
	_gtk_xembed_set_focus_wrapped ();
1252 1253
    }

1254 1255
  focus_widget = gtk_window_get_focus (window);
  if (window)
1256 1257
    {
      /* Wrapped off the end, clear the focus setting for the toplevel */
1258
      parent = gtk_widget_get_parent (focus_widget);
1259 1260 1261
      while (parent)
	{
	  gtk_container_set_focus_child (GTK_CONTAINER (parent), NULL);
1262
          parent = gtk_widget_get_parent (parent);
1263 1264 1265 1266 1267 1268
	}
      
      gtk_window_set_focus (GTK_WINDOW (container), NULL);
    }

  /* Now try to focus the first widget in the window */
Javier Jardón's avatar
Javier Jardón committed
1269 1270
  child = gtk_bin_get_child (bin);
  if (child)
1271
    {
Javier Jardón's avatar
Javier Jardón committed
1272
      if (gtk_widget_child_focus (child, direction))
1273 1274 1275
        return;
    }
}
1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431