[gtk/wip/chergert/macos-fix-toplevel-resize] macos: fix configure, move-resize, and compute-size




commit 4c08d1643f4b92b5e3c53c5f02a3508d42625cba
Author: Christian Hergert <christian hergert me>
Date:   Wed Feb 16 02:50:51 2022 -0800

    macos: fix configure, move-resize, and compute-size
    
    This significantly cleans up how we handle various move-resize, compute-
    size, and configure (notification of changes) in the macOS GDK backend.
    
    Originally when prototyping this backend, there were some bits that came
    over from the quartz backend and some bits which did not. It got confusing
    and so this makes an attempt to knock down all that technical debt.
    
    It is much simpler now in that the GdkMacosSurface makes requests of the
    GdkMacosWindow, and the GdkMacosWindow notifies the GdkMacosSurface of
    changes that happen.
    
    User resizes are delayed until the next compute-size so that we are much
    closer to the layout phase, reducing chances for in-between frames.
    
    This also improves the situation of leaving maximized state so that a
    grab and drag feels like you'd expect on other platforms.
    
    I removed the opacity hack we had in before, because that is all coming
    out anyway and it's a bit obnoxious to maintain through the async flows
    here.

 gdk/macos/GdkMacosWindow.c                  | 176 ++++++++++------------------
 gdk/macos/gdkmacosdisplay.c                 |   2 +-
 gdk/macos/gdkmacossurface-private.h         |  11 +-
 gdk/macos/gdkmacossurface.c                 |  98 ++++++++++------
 gdk/macos/gdkmacostoplevelsurface-private.h |   2 +
 gdk/macos/gdkmacostoplevelsurface.c         |  60 ++++++++--
 6 files changed, 188 insertions(+), 161 deletions(-)
---
diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c
index 8dda79ab4d..0bb7521f16 100644
--- a/gdk/macos/GdkMacosWindow.c
+++ b/gdk/macos/GdkMacosWindow.c
@@ -203,76 +203,20 @@ typedef NSString *CALayerContentsGravity;
     }
 }
 
--(void)windowDidUnmaximize
+-(void)setFrame:(NSRect)frame display:(BOOL)display
 {
-  NSWindowStyleMask style_mask = [self styleMask];
-
-  gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_TOPLEVEL_STATE_MAXIMIZED, 0);
+  NSRect contentRect = [self contentRectForFrameRect:frame];
+  GdkSurface *surface = GDK_SURFACE (gdk_surface);
+  gboolean maximized = (surface->state & GDK_TOPLEVEL_STATE_MAXIMIZED) != 0;
 
-  /* If we are using CSD, then we transitioned to an opaque
-   * window while we were maximized. Now we need to drop that
-   * as we are leaving maximized state.
-   */
-  if ((style_mask & NSWindowStyleMaskTitled) == 0 && [self isOpaque])
+  if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, frame))
     {
-      GdkSurface *surface = GDK_SURFACE ([self gdkSurface]);
-
-      [self setOpaque:NO];
-
-      /* Force updating of various styling, regions, etc */
+      gdk_synthesize_surface_state (surface, GDK_TOPLEVEL_STATE_MAXIMIZED, 0);
       _gdk_surface_update_size (surface);
     }
-}
 
--(void)windowDidMove:(NSNotification *)aNotification
-{
-  GdkSurface *surface = GDK_SURFACE (gdk_surface);
-  gboolean maximized = (surface->state & GDK_TOPLEVEL_STATE_MAXIMIZED) != 0;
-
-  /* In case the window is changed when maximized remove the maximized state */
-  if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
-    [self windowDidUnmaximize];
-
-  _gdk_macos_surface_update_position (gdk_surface);
-  _gdk_macos_surface_reposition_children (gdk_surface);
-
-  [self checkSendEnterNotify];
-}
-
--(void)windowDidResize:(NSNotification *)aNotification
-{
-  NSRect content_rect;
-  GdkSurface *surface;
-  GdkDisplay *display;
-  gboolean maximized;
-
-  surface = GDK_SURFACE (gdk_surface);
-  display = gdk_surface_get_display (surface);
-
-  content_rect = [self contentRectForFrameRect:[self frame]];
-  maximized = (surface->state & GDK_TOPLEVEL_STATE_MAXIMIZED) != 0;
-
-  /* see same in windowDidMove */
-  if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
-    [self windowDidUnmaximize];
-
-  surface->width = content_rect.size.width;
-  surface->height = content_rect.size.height;
-
-  /* Certain resize operations (e.g. going fullscreen), also move the
-   * origin of the window.
-   */
-  _gdk_macos_surface_update_position (GDK_MACOS_SURFACE (surface));
-
-  [[self contentView] setFrame:NSMakeRect (0, 0, surface->width, surface->height)];
-
-  _gdk_surface_update_size (surface);
-
-  gdk_surface_request_layout (surface);
-
-  _gdk_macos_surface_reposition_children (gdk_surface);
-
-  [self checkSendEnterNotify];
+  [super setFrame:frame display:display];
+  [[self contentView] setFrame:NSMakeRect (0, 0, contentRect.size.width, contentRect.size.height)];
 }
 
 -(id)initWithContentRect:(NSRect)contentRect
@@ -418,12 +362,23 @@ typedef NSString *CALayerContentsGravity;
   windowFrame.origin.x = new_origin.x;
   windowFrame.origin.y = new_origin.y;
 
-  /* And now apply the frame to the window */
-  [self setFrameOrigin:NSMakePoint(new_origin.x, new_origin.y)];
+  [self setFrame:NSMakeRect (new_origin.x, new_origin.y,
+                             window_gdk.width, window_gdk.height)
+         display:YES];
 
   return YES;
 }
 
+-(void)windowDidMove:(NSNotification *)notification
+{
+  _gdk_macos_surface_configure ([self gdkSurface]);
+}
+
+- (void)windowDidResize:(NSNotification *)notification
+{
+  _gdk_macos_surface_configure ([self gdkSurface]);
+}
+
 /* Used by gdkmacosdisplay-translate.c to decide if our sendEvent() handler
  * above will see the event or if it will be subjected to standard processing
  * by GDK.
@@ -435,6 +390,7 @@ typedef NSString *CALayerContentsGravity;
 
 -(void)beginManualMove
 {
+  gboolean maximized = GDK_SURFACE (gdk_surface)->state & GDK_TOPLEVEL_STATE_MAXIMIZED;
   NSPoint initialMoveLocation;
   GdkPoint point;
   GdkMonitor *monitor;
@@ -453,6 +409,13 @@ typedef NSString *CALayerContentsGravity;
 
   initialMoveLocation = [NSEvent mouseLocation];
 
+  if (maximized)
+    [self setFrame:NSMakeRect (initialMoveLocation.x - (int)lastUnmaximizedFrame.size.width/2,
+                               initialMoveLocation.y,
+                               lastUnmaximizedFrame.size.width,
+                               lastUnmaximizedFrame.size.height)
+           display:YES];
+
   _gdk_macos_display_from_display_coords ([self gdkDisplay],
                                           initialMoveLocation.x,
                                           initialMoveLocation.y,
@@ -544,15 +507,7 @@ typedef NSString *CALayerContentsGravity;
       new_frame.size.height = min_size.height;
     }
 
-  /* We could also apply aspect ratio:
-     new_frame.size.height = new_frame.size.width / [self aspectRatio].width * [self aspectRatio].height;
-  */
-
-  [self setFrame:new_frame display:YES];
-
-  /* Let the resizing be handled by GTK. */
-  if (g_main_context_pending (NULL))
-    g_main_context_iteration (NULL, FALSE);
+  _gdk_macos_surface_user_resize ([self gdkSurface], new_frame);
 
   inTrackManualResize = NO;
 
@@ -561,49 +516,46 @@ typedef NSString *CALayerContentsGravity;
 
 -(void)beginManualResize:(GdkSurfaceEdge)edge
 {
+  CALayerContentsGravity gravity = kCAGravityBottomLeft;
+
   if (inMove || inManualMove || inManualResize)
     return;
 
   inManualResize = YES;
   resizeEdge = edge;
 
-  if (GDK_IS_MACOS_GL_VIEW ([self contentView]))
+  switch (edge)
     {
-      CALayerContentsGravity gravity = kCAGravityBottomLeft;
+    default:
+    case GDK_SURFACE_EDGE_NORTH:
+      gravity = kCAGravityTopLeft;
+      break;
 
-      switch (edge)
-        {
-        default:
-        case GDK_SURFACE_EDGE_NORTH:
-          gravity = kCAGravityTopLeft;
-          break;
-
-        case GDK_SURFACE_EDGE_NORTH_WEST:
-          gravity = kCAGravityTopRight;
-          break;
-
-        case GDK_SURFACE_EDGE_SOUTH_WEST:
-        case GDK_SURFACE_EDGE_WEST:
-          gravity = kCAGravityBottomRight;
-          break;
-
-        case GDK_SURFACE_EDGE_SOUTH:
-        case GDK_SURFACE_EDGE_SOUTH_EAST:
-          gravity = kCAGravityBottomLeft;
-          break;
-
-        case GDK_SURFACE_EDGE_EAST:
-          gravity = kCAGravityBottomLeft;
-          break;
-
-        case GDK_SURFACE_EDGE_NORTH_EAST:
-          gravity = kCAGravityTopLeft;
-          break;
-        }
+    case GDK_SURFACE_EDGE_NORTH_WEST:
+      gravity = kCAGravityTopRight;
+      break;
+
+    case GDK_SURFACE_EDGE_SOUTH_WEST:
+    case GDK_SURFACE_EDGE_WEST:
+      gravity = kCAGravityBottomRight;
+      break;
+
+    case GDK_SURFACE_EDGE_SOUTH:
+    case GDK_SURFACE_EDGE_SOUTH_EAST:
+      gravity = kCAGravityBottomLeft;
+      break;
+
+    case GDK_SURFACE_EDGE_EAST:
+      gravity = kCAGravityBottomLeft;
+      break;
 
-      [[[self contentView] layer] setContentsGravity:gravity];
+    case GDK_SURFACE_EDGE_NORTH_EAST:
+      gravity = kCAGravityTopLeft;
+      break;
     }
 
+  [[[self contentView] layer] setContentsGravity:gravity];
+
   initialResizeFrame = [self frame];
   initialResizeLocation = convert_nspoint_to_screen (self, [self mouseLocationOutsideOfEventStream]);
 }
@@ -767,7 +719,6 @@ typedef NSString *CALayerContentsGravity;
   if (state & GDK_TOPLEVEL_STATE_MAXIMIZED)
     {
       lastMaximizedFrame = newFrame;
-      [self windowDidUnmaximize];
     }
   else
     {
@@ -782,16 +733,7 @@ typedef NSString *CALayerContentsGravity;
 
 -(void)windowDidEndLiveResize:(NSNotification *)aNotification
 {
-  gboolean maximized = GDK_SURFACE (gdk_surface)->state & GDK_TOPLEVEL_STATE_MAXIMIZED;
-
   inMaximizeTransition = NO;
-
-  /* Even if this is CSD, we want to be opaque while maximized
-   * to speed up compositing by allowing the display server to
-   * avoid costly blends.
-   */
-  if (maximized)
-    [self setOpaque:YES];
 }
 
 -(NSSize)window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize
diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c
index 8d3aa0224e..656ceb8097 100644
--- a/gdk/macos/gdkmacosdisplay.c
+++ b/gdk/macos/gdkmacosdisplay.c
@@ -185,7 +185,7 @@ gdk_macos_display_monitors_changed_cb (CFNotificationCenterRef  center,
       g_assert (GDK_IS_MACOS_SURFACE (surface));
 
       if (GDK_IS_TOPLEVEL (surface))
-        _gdk_macos_surface_update_position (surface);
+        _gdk_macos_surface_configure (surface);
     }
 }
 
diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h
index ccb8a83179..0cfe0a8a2f 100644
--- a/gdk/macos/gdkmacossurface-private.h
+++ b/gdk/macos/gdkmacossurface-private.h
@@ -53,6 +53,13 @@ struct _GdkMacosSurface
   int root_x;
   int root_y;
 
+  struct {
+    int root_x;
+    int root_y;
+    int width;
+    int height;
+  } next_layout;
+
   int shadow_top;
   int shadow_right;
   int shadow_bottom;
@@ -102,7 +109,6 @@ void               _gdk_macos_surface_resize                  (GdkMacosSurface
                                                                int                 width,
                                                                int                 height);
 void               _gdk_macos_surface_update_fullscreen_state (GdkMacosSurface    *self);
-void               _gdk_macos_surface_update_position         (GdkMacosSurface    *self);
 void               _gdk_macos_surface_show                    (GdkMacosSurface    *self);
 void               _gdk_macos_surface_publish_timings         (GdkMacosSurface    *self,
                                                                gint64              
predicted_presentation_time,
@@ -121,6 +127,9 @@ void               _gdk_macos_surface_move_resize             (GdkMacosSurface
                                                                int                 y,
                                                                int                 width,
                                                                int                 height);
+void               _gdk_macos_surface_configure               (GdkMacosSurface    *self);
+void               _gdk_macos_surface_user_resize             (GdkMacosSurface    *self,
+                                                               CGRect              new_frame);
 gboolean           _gdk_macos_surface_is_tracking             (GdkMacosSurface    *self,
                                                                NSTrackingArea     *area);
 void               _gdk_macos_surface_monitor_changed         (GdkMacosSurface    *self);
diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c
index c5cf05edf8..967cbd94b4 100644
--- a/gdk/macos/gdkmacossurface.c
+++ b/gdk/macos/gdkmacossurface.c
@@ -419,15 +419,6 @@ gdk_macos_surface_constructed (GObject *object)
 
   G_OBJECT_CLASS (gdk_macos_surface_parent_class)->constructed (object);
 
-  if (self->window != NULL)
-    {
-      NSRect bounds = [[self->window contentView] bounds];
-
-      GDK_SURFACE (self)->width = bounds.size.width;
-      GDK_SURFACE (self)->height = bounds.size.height;
-      _gdk_macos_surface_update_position (self);
-    }
-
   if ((frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self))))
     {
       g_signal_connect_object (frame_clock,
@@ -441,6 +432,9 @@ gdk_macos_surface_constructed (GObject *object)
                                self,
                                G_CONNECT_SWAPPED);
     }
+
+  if (self->window != NULL)
+    _gdk_macos_surface_configure (self);
 }
 
 static void
@@ -699,12 +693,21 @@ _gdk_macos_surface_update_fullscreen_state (GdkMacosSurface *self)
 }
 
 void
-_gdk_macos_surface_update_position (GdkMacosSurface *self)
+_gdk_macos_surface_configure (GdkMacosSurface *self)
 {
-  GdkSurface *surface = GDK_SURFACE (self);
-  GdkDisplay *display = gdk_surface_get_display (surface);
-  NSRect frame_rect = [self->window frame];
-  NSRect content_rect = [self->window contentRectForFrameRect:frame_rect];
+  GdkMacosDisplay *display;
+  GdkSurface *surface = (GdkSurface *)self;
+  NSRect frame_rect;
+  NSRect content_rect;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (self))
+    return;
+
+  display = GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display);
+  frame_rect = [self->window frame];
+  content_rect = [self->window contentRectForFrameRect:frame_rect];
 
   _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
                                           content_rect.origin.x,
@@ -721,6 +724,19 @@ _gdk_macos_surface_update_position (GdkMacosSurface *self)
       surface->x = self->root_x;
       surface->y = self->root_y;
     }
+
+  if (surface->width != content_rect.size.width ||
+      surface->height != content_rect.size.height)
+    {
+      surface->width = content_rect.size.width;
+      surface->height = content_rect.size.height;
+
+      _gdk_surface_update_size (surface);
+      gdk_surface_request_layout (surface);
+      gdk_surface_invalidate_rect (surface, NULL);
+    }
+
+  _gdk_macos_surface_reposition_children (self);
 }
 
 void
@@ -781,8 +797,7 @@ _gdk_macos_surface_show (GdkMacosSurface *self)
     {
       if (gdk_surface_get_mapped (GDK_SURFACE (self)))
         {
-          _gdk_macos_surface_update_position (self);
-          gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL);
+          _gdk_macos_surface_configure (self);
           gdk_surface_thaw_updates (GDK_SURFACE (self));
         }
     }
@@ -882,7 +897,6 @@ _gdk_macos_surface_move_resize (GdkMacosSurface *self,
   GdkDisplay *display;
   NSRect content_rect;
   NSRect frame_rect;
-  gboolean size_changed;
 
   g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
 
@@ -906,28 +920,43 @@ _gdk_macos_surface_move_resize (GdkMacosSurface *self,
   if (y == -1)
     y = self->root_y;
 
-  size_changed = height != surface->height || width != surface->width;
-
-  if (GDK_IS_MACOS_SURFACE (surface->parent))
-    {
-      surface->x = x - GDK_MACOS_SURFACE (surface->parent)->root_x;
-      surface->y = y - GDK_MACOS_SURFACE (surface->parent)->root_y;
-    }
-  else
-    {
-      surface->x = x;
-      surface->y = y;
-    }
-
   _gdk_macos_display_to_display_coords (GDK_MACOS_DISPLAY (display),
-                                        x, y + height, &x, &y);
+                                        x, y + height,
+                                        &x, &y);
 
   content_rect = NSMakeRect (x, y, width, height);
   frame_rect = [self->window frameRectForContentRect:content_rect];
   [self->window setFrame:frame_rect display:YES];
+}
+
+void
+_gdk_macos_surface_user_resize (GdkMacosSurface *self,
+                                CGRect           new_frame)
+{
+  GdkMacosDisplay *display;
+  CGRect content_rect;
+  int root_x, root_y;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+  g_return_if_fail (GDK_IS_TOPLEVEL (self));
+
+  if (GDK_SURFACE_DESTROYED (self))
+    return;
+
+  display = GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display);
+  content_rect = [self->window contentRectForFrameRect:new_frame];
+
+  _gdk_macos_display_from_display_coords (display,
+                                          new_frame.origin.x,
+                                          new_frame.origin.y + new_frame.size.height,
+                                          &root_x, &root_y);
 
-  if (size_changed)
-    gdk_surface_invalidate_rect (surface, NULL);
+  self->next_layout.root_x = root_x;
+  self->next_layout.root_y = root_y;
+  self->next_layout.width = content_rect.size.width;
+  self->next_layout.height = content_rect.size.height;
+
+  gdk_surface_request_layout (GDK_SURFACE (self));
 }
 
 gboolean
@@ -995,7 +1024,8 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self)
       g_object_unref (monitor);
     }
 
-  _gdk_surface_update_size (GDK_SURFACE (self));
+  _gdk_macos_surface_configure (self);
+
   gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL);
 }
 
diff --git a/gdk/macos/gdkmacostoplevelsurface-private.h b/gdk/macos/gdkmacostoplevelsurface-private.h
index b0bddfa0c1..1404aa14fe 100644
--- a/gdk/macos/gdkmacostoplevelsurface-private.h
+++ b/gdk/macos/gdkmacostoplevelsurface-private.h
@@ -35,6 +35,8 @@ struct _GdkMacosToplevelSurface
 {
   GdkMacosSurface    parent_instance;
   GdkToplevelLayout *layout;
+  int                last_computed_width;
+  int                last_computed_height;
   guint              decorated : 1;
 };
 
diff --git a/gdk/macos/gdkmacostoplevelsurface.c b/gdk/macos/gdkmacostoplevelsurface.c
index 17b1cec708..bb2a77f256 100644
--- a/gdk/macos/gdkmacostoplevelsurface.c
+++ b/gdk/macos/gdkmacostoplevelsurface.c
@@ -381,11 +381,11 @@ static gboolean
 _gdk_macos_toplevel_surface_compute_size (GdkSurface *surface)
 {
   GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)surface;
+  GdkMacosSurface *macos_surface = (GdkMacosSurface *)surface;
   GdkToplevelSize size;
   GdkDisplay *display;
   GdkMonitor *monitor;
   int bounds_width, bounds_height;
-  int width, height;
   GdkGeometry geometry;
   GdkSurfaceHints mask;
 
@@ -419,9 +419,6 @@ _gdk_macos_toplevel_surface_compute_size (GdkSurface *surface)
   g_warn_if_fail (size.width > 0);
   g_warn_if_fail (size.height > 0);
 
-  width = surface->width;
-  height = surface->height;
-
   if (self->layout != NULL &&
       gdk_toplevel_layout_get_resizable (self->layout))
     {
@@ -437,16 +434,63 @@ _gdk_macos_toplevel_surface_compute_size (GdkSurface *surface)
     }
 
   if (size.shadow.is_valid)
-    _gdk_macos_surface_set_shadow (GDK_MACOS_SURFACE (surface),
+    _gdk_macos_surface_set_shadow (macos_surface,
                                    size.shadow.top,
                                    size.shadow.right,
                                    size.shadow.bottom,
                                    size.shadow.left);
 
-  gdk_surface_constrain_size (&geometry, mask, width, height, &width, &height);
+  _gdk_macos_surface_set_geometry_hints (macos_surface, &geometry, mask);
 
-  _gdk_macos_surface_set_geometry_hints (GDK_MACOS_SURFACE (self), &geometry, mask);
-  _gdk_macos_surface_resize (GDK_MACOS_SURFACE (self), width, height);
+  if (surface->state & (GDK_TOPLEVEL_STATE_FULLSCREEN |
+                        GDK_TOPLEVEL_STATE_MAXIMIZED |
+                        GDK_TOPLEVEL_STATE_TILED |
+                        GDK_TOPLEVEL_STATE_TOP_TILED |
+                        GDK_TOPLEVEL_STATE_RIGHT_TILED |
+                        GDK_TOPLEVEL_STATE_BOTTOM_TILED |
+                        GDK_TOPLEVEL_STATE_LEFT_TILED |
+                        GDK_TOPLEVEL_STATE_MINIMIZED))
+    return FALSE;
+
+  /* If we delayed a user resize until the beginning of the frame,
+   * apply it now so we can start processing updates for it.
+   */
+  if (macos_surface->next_layout.width > 0 &&
+      macos_surface->next_layout.height > 0)
+    {
+      int root_x = macos_surface->next_layout.root_x;
+      int root_y = macos_surface->next_layout.root_y;
+      int width = macos_surface->next_layout.width;
+      int height = macos_surface->next_layout.height;
+
+      gdk_surface_constrain_size (&geometry, mask,
+                                  width, height,
+                                  &width, &height);
+
+      macos_surface->next_layout.width = 0;
+      macos_surface->next_layout.height = 0;
+
+      _gdk_macos_surface_move_resize (macos_surface,
+                                      root_x, root_y,
+                                      width, height);
+
+      return FALSE;
+    }
+
+  gdk_surface_constrain_size (&geometry, mask,
+                              size.width, size.height,
+                              &size.width, &size.height);
+
+  if ((size.width != self->last_computed_width ||
+       size.height != self->last_computed_height) &&
+      (size.width != surface->width ||
+       size.height != surface->height))
+    {
+      self->last_computed_width = size.width;
+      self->last_computed_height = size.height;
+
+      _gdk_macos_surface_resize (macos_surface, size.width, size.height);
+    }
 
   return FALSE;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]