gtk2/gdk/quartz/GdkQuartzNSWindow.c
Michael Natterer 2d5ad5f54e Bug 672193 - windows (including menus) shown multiple times don't...
Based on a patch from Paul Davis, inject synthetic enter events directly
into the Quartz event stream, instead of trying to synthesize them in GDK.

This seems to magically fix most combo box popup weirdness, I guess
some code is relying on a specfic order of events, or any other state
imposed by the "proper" code path of events coming in the usual way.

The patch also removes _gdk_quartz_events_send_enter_notify_event()
which is now obsolete.

(sortof cherry-pixked from 979e5061a0
but needed manual editing because GdkQuartzWindow.c was renamed
and apparently earlier patches not picked correctly/completely)
2012-11-20 10:28:58 +01:00

652 lines
18 KiB
C

/* GdkQuartzWindow.m
*
* Copyright (C) 2005-2007 Imendio AB
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#import "GdkQuartzNSWindow.h"
#include "gdkquartzwindow.h"
#include "gdkdnd-quartz.h"
#include "gdkprivate-quartz.h"
@implementation GdkQuartzNSWindow
-(BOOL)windowShouldClose:(id)sender
{
GdkWindow *window = [[self contentView] gdkWindow];
GdkEvent *event;
event = gdk_event_new (GDK_DELETE);
event->any.window = g_object_ref (window);
event->any.send_event = FALSE;
_gdk_event_queue_append (gdk_display_get_default (), event);
return NO;
}
-(void)windowWillMiniaturize:(NSNotification *)aNotification
{
GdkWindow *window = [[self contentView] gdkWindow];
_gdk_quartz_window_detach_from_parent (window);
}
-(void)windowDidMiniaturize:(NSNotification *)aNotification
{
GdkWindow *window = [[self contentView] gdkWindow];
gdk_synthesize_window_state (window, 0,
GDK_WINDOW_STATE_ICONIFIED);
}
-(void)windowDidDeminiaturize:(NSNotification *)aNotification
{
GdkWindow *window = [[self contentView] gdkWindow];
_gdk_quartz_window_attach_to_parent (window);
gdk_synthesize_window_state (window, GDK_WINDOW_STATE_ICONIFIED, 0);
}
-(void)windowDidBecomeKey:(NSNotification *)aNotification
{
GdkWindow *window = [[self contentView] gdkWindow];
_gdk_quartz_events_update_focus_window (window, TRUE);
}
-(void)windowDidResignKey:(NSNotification *)aNotification
{
GdkWindow *window = [[self contentView] gdkWindow];
_gdk_quartz_events_update_focus_window (window, FALSE);
}
-(void)windowDidBecomeMain:(NSNotification *)aNotification
{
GdkWindow *window = [[self contentView] gdkWindow];
if (![self isVisible])
{
/* Note: This is a hack needed because for unknown reasons, hidden
* windows get shown when clicking the dock icon when the application
* is not already active.
*/
[self orderOut:nil];
return;
}
_gdk_quartz_window_did_become_main (window);
}
-(void)windowDidResignMain:(NSNotification *)aNotification
{
GdkWindow *window;
window = [[self contentView] gdkWindow];
_gdk_quartz_window_did_resign_main (window);
}
/* Used in combination with NSLeftMouseUp in sendEvent to keep track
* of when the window is being moved with the mouse.
*/
-(void)windowWillMove:(NSNotification *)aNotification
{
inMove = YES;
}
-(void)sendEvent:(NSEvent *)event
{
switch ([event type])
{
case NSLeftMouseUp:
{
double time = ((double)[event timestamp]) * 1000.0;
_gdk_quartz_events_break_all_grabs (time);
inManualMove = NO;
inManualResize = NO;
inMove = NO;
break;
}
case NSLeftMouseDragged:
if ([self trackManualMove] || [self trackManualResize])
return;
break;
default:
break;
}
[super sendEvent:event];
}
-(BOOL)isInMove
{
return inMove;
}
-(void)checkSendEnterNotify
{
GdkWindow *window = [[self contentView] gdkWindow];
GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl);
/* When a new window has been created, and the mouse
* is in the window area, we will not receive an NSMouseEntered
* event. Therefore, we synthesize an enter notify event manually.
*/
if (!initialPositionKnown)
{
initialPositionKnown = YES;
if (NSPointInRect ([NSEvent mouseLocation], [self frame]))
{
NSEvent *event;
event = [NSEvent enterExitEventWithType: NSMouseEntered
location: [self mouseLocationOutsideOfEventStream]
modifierFlags: 0
timestamp: [[NSApp currentEvent] timestamp]
windowNumber: [impl->toplevel windowNumber]
context: NULL
eventNumber: 0
trackingNumber: [impl->view trackingRect]
userData: nil];
[NSApp postEvent:event atStart:NO];
}
}
}
-(void)windowDidMove:(NSNotification *)aNotification
{
GdkWindow *window = [[self contentView] gdkWindow];
GdkEvent *event;
_gdk_quartz_window_update_position (window);
/* Synthesize a configure event */
event = gdk_event_new (GDK_CONFIGURE);
event->configure.window = g_object_ref (window);
event->configure.x = window->x;
event->configure.y = window->y;
event->configure.width = window->width;
event->configure.height = window->height;
_gdk_event_queue_append (gdk_display_get_default (), event);
[self checkSendEnterNotify];
}
-(void)windowDidResize:(NSNotification *)aNotification
{
NSRect content_rect = [self contentRectForFrameRect:[self frame]];
GdkWindow *window = [[self contentView] gdkWindow];
GdkEvent *event;
window->width = content_rect.size.width;
window->height = content_rect.size.height;
/* Certain resize operations (e.g. going fullscreen), also move the
* origin of the window.
*/
_gdk_quartz_window_update_position (window);
[[self contentView] setFrame:NSMakeRect (0, 0, window->width, window->height)];
_gdk_window_update_size (window);
/* Synthesize a configure event */
event = gdk_event_new (GDK_CONFIGURE);
event->configure.window = g_object_ref (window);
event->configure.x = window->x;
event->configure.y = window->y;
event->configure.width = window->width;
event->configure.height = window->height;
_gdk_event_queue_append (gdk_display_get_default (), event);
[self checkSendEnterNotify];
}
-(id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag screen:(NSScreen *)screen
{
self = [super initWithContentRect:contentRect
styleMask:styleMask
backing:backingType
defer:flag
screen:screen];
[self setAcceptsMouseMovedEvents:YES];
[self setDelegate:self];
[self setReleasedWhenClosed:YES];
return self;
}
-(BOOL)canBecomeMainWindow
{
GdkWindow *window = [[self contentView] gdkWindow];
GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl);
switch (impl->type_hint)
{
case GDK_WINDOW_TYPE_HINT_NORMAL:
case GDK_WINDOW_TYPE_HINT_DIALOG:
return YES;
case GDK_WINDOW_TYPE_HINT_MENU:
case GDK_WINDOW_TYPE_HINT_TOOLBAR:
case GDK_WINDOW_TYPE_HINT_SPLASHSCREEN:
case GDK_WINDOW_TYPE_HINT_UTILITY:
case GDK_WINDOW_TYPE_HINT_DOCK:
case GDK_WINDOW_TYPE_HINT_DESKTOP:
case GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU:
case GDK_WINDOW_TYPE_HINT_POPUP_MENU:
case GDK_WINDOW_TYPE_HINT_TOOLTIP:
case GDK_WINDOW_TYPE_HINT_NOTIFICATION:
case GDK_WINDOW_TYPE_HINT_COMBO:
case GDK_WINDOW_TYPE_HINT_DND:
return NO;
}
return YES;
}
-(BOOL)canBecomeKeyWindow
{
GdkWindow *window = [[self contentView] gdkWindow];
GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl);
if (!window->accept_focus)
return NO;
/* Popup windows should not be able to get focused in the window
* manager sense, it's only handled through grabs.
*/
if (window->window_type == GDK_WINDOW_TEMP)
return NO;
switch (impl->type_hint)
{
case GDK_WINDOW_TYPE_HINT_NORMAL:
case GDK_WINDOW_TYPE_HINT_DIALOG:
case GDK_WINDOW_TYPE_HINT_MENU:
case GDK_WINDOW_TYPE_HINT_TOOLBAR:
case GDK_WINDOW_TYPE_HINT_UTILITY:
case GDK_WINDOW_TYPE_HINT_DOCK:
case GDK_WINDOW_TYPE_HINT_DESKTOP:
case GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU:
case GDK_WINDOW_TYPE_HINT_POPUP_MENU:
case GDK_WINDOW_TYPE_HINT_COMBO:
return YES;
case GDK_WINDOW_TYPE_HINT_SPLASHSCREEN:
case GDK_WINDOW_TYPE_HINT_TOOLTIP:
case GDK_WINDOW_TYPE_HINT_NOTIFICATION:
case GDK_WINDOW_TYPE_HINT_DND:
return NO;
}
return YES;
}
- (void)showAndMakeKey:(BOOL)makeKey
{
GdkWindow *window = [[self contentView] gdkWindow];
GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl);
inShowOrHide = YES;
if (makeKey)
[impl->toplevel makeKeyAndOrderFront:impl->toplevel];
else
[impl->toplevel orderFront:nil];
inShowOrHide = NO;
[self checkSendEnterNotify];
}
- (void)hide
{
GdkWindow *window = [[self contentView] gdkWindow];
GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl);
inShowOrHide = YES;
[impl->toplevel orderOut:nil];
inShowOrHide = NO;
initialPositionKnown = NO;
}
- (BOOL)trackManualMove
{
NSPoint currentLocation;
NSPoint newOrigin;
NSRect screenFrame = [[NSScreen mainScreen] visibleFrame];
NSRect windowFrame = [self frame];
if (!inManualMove)
return NO;
currentLocation = [self convertBaseToScreen:[self mouseLocationOutsideOfEventStream]];
newOrigin.x = currentLocation.x - initialMoveLocation.x;
newOrigin.y = currentLocation.y - initialMoveLocation.y;
/* Clamp vertical position to below the menu bar. */
if (newOrigin.y + windowFrame.size.height > screenFrame.origin.y + screenFrame.size.height)
newOrigin.y = screenFrame.origin.y + screenFrame.size.height - windowFrame.size.height;
[self setFrameOrigin:newOrigin];
return YES;
}
-(BOOL)isInManualResize
{
return inManualResize;
}
-(void)beginManualMove
{
NSRect frame = [self frame];
if (inMove || inManualMove || inManualResize)
return;
inManualMove = YES;
initialMoveLocation = [self convertBaseToScreen:[self mouseLocationOutsideOfEventStream]];
initialMoveLocation.x -= frame.origin.x;
initialMoveLocation.y -= frame.origin.y;
}
- (BOOL)trackManualResize
{
NSPoint currentLocation;
NSRect newFrame;
float dx, dy;
NSSize min_size;
if (!inManualResize || inTrackManualResize)
return NO;
inTrackManualResize = YES;
currentLocation = [self convertBaseToScreen:[self mouseLocationOutsideOfEventStream]];
currentLocation.x -= initialResizeFrame.origin.x;
currentLocation.y -= initialResizeFrame.origin.y;
dx = currentLocation.x - initialResizeLocation.x;
dy = -(currentLocation.y - initialResizeLocation.y);
newFrame = initialResizeFrame;
newFrame.size.width = initialResizeFrame.size.width + dx;
newFrame.size.height = initialResizeFrame.size.height + dy;
min_size = [self contentMinSize];
if (newFrame.size.width < min_size.width)
newFrame.size.width = min_size.width;
if (newFrame.size.height < min_size.height)
newFrame.size.height = min_size.height;
/* We could also apply aspect ratio:
newFrame.size.height = newFrame.size.width / [self aspectRatio].width * [self aspectRatio].height;
*/
dy = newFrame.size.height - initialResizeFrame.size.height;
newFrame.origin.x = initialResizeFrame.origin.x;
newFrame.origin.y = initialResizeFrame.origin.y - dy;
[self setFrame:newFrame display:YES];
/* Let the resizing be handled by GTK+. */
if (g_main_context_pending (NULL))
g_main_context_iteration (NULL, FALSE);
inTrackManualResize = NO;
return YES;
}
-(void)beginManualResize
{
if (inMove || inManualMove || inManualResize)
return;
inManualResize = YES;
initialResizeFrame = [self frame];
initialResizeLocation = [self convertBaseToScreen:[self mouseLocationOutsideOfEventStream]];
initialResizeLocation.x -= initialResizeFrame.origin.x;
initialResizeLocation.y -= initialResizeFrame.origin.y;
}
static GdkDragContext *current_context = NULL;
static GdkDragAction
drag_operation_to_drag_action (NSDragOperation operation)
{
GdkDragAction result = 0;
/* GDK and Quartz drag operations do not map 1:1.
* This mapping represents about the best that we
* can come up.
*
* Note that NSDragOperationPrivate and GDK_ACTION_PRIVATE
* have almost opposite meanings: the GDK one means that the
* destination is solely responsible for the action; the Quartz
* one means that the source and destination will agree
* privately on the action. NSOperationGeneric is close in meaning
* to GDK_ACTION_PRIVATE but there is a problem: it will be
* sent for any ordinary drag, and likely not understood
* by any intra-widget drag (since the source & dest are the
* same).
*/
if (operation & NSDragOperationGeneric)
result |= GDK_ACTION_MOVE;
if (operation & NSDragOperationCopy)
result |= GDK_ACTION_COPY;
if (operation & NSDragOperationMove)
result |= GDK_ACTION_MOVE;
if (operation & NSDragOperationLink)
result |= GDK_ACTION_LINK;
return result;
}
static NSDragOperation
drag_action_to_drag_operation (GdkDragAction action)
{
NSDragOperation result = 0;
if (action & GDK_ACTION_COPY)
result |= NSDragOperationCopy;
if (action & GDK_ACTION_LINK)
result |= NSDragOperationLink;
if (action & GDK_ACTION_MOVE)
result |= NSDragOperationMove;
return result;
}
static void
update_context_from_dragging_info (id <NSDraggingInfo> sender)
{
g_assert (current_context != NULL);
GDK_QUARTZ_DRAG_CONTEXT (current_context)->dragging_info = sender;
current_context->suggested_action = drag_operation_to_drag_action ([sender draggingSourceOperationMask]);
current_context->actions = current_context->suggested_action;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
GdkDeviceManager *device_manager;
GdkEvent *event;
GdkWindow *window;
if (current_context)
g_object_unref (current_context);
current_context = g_object_new (GDK_TYPE_QUARTZ_DRAG_CONTEXT, NULL);
update_context_from_dragging_info (sender);
window = [[self contentView] gdkWindow];
device_manager = gdk_display_get_device_manager (gdk_display_get_default ());
gdk_drag_context_set_device (current_context,
gdk_device_manager_get_client_pointer (device_manager));
event = gdk_event_new (GDK_DRAG_ENTER);
event->dnd.window = g_object_ref (window);
event->dnd.send_event = FALSE;
event->dnd.context = g_object_ref (current_context);
event->dnd.time = GDK_CURRENT_TIME;
gdk_event_set_device (event, gdk_drag_context_get_device (current_context));
_gdk_event_emit (event);
gdk_event_free (event);
return NSDragOperationNone;
}
- (void)draggingEnded:(id <NSDraggingInfo>)sender
{
/* leave a note for the source about what action was taken */
if (_gdk_quartz_drag_source_context && current_context)
_gdk_quartz_drag_source_context->action = current_context->action;
if (current_context)
g_object_unref (current_context);
current_context = NULL;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender
{
GdkEvent *event;
event = gdk_event_new (GDK_DRAG_LEAVE);
event->dnd.window = g_object_ref ([[self contentView] gdkWindow]);
event->dnd.send_event = FALSE;
event->dnd.context = g_object_ref (current_context);
event->dnd.time = GDK_CURRENT_TIME;
gdk_event_set_device (event, gdk_drag_context_get_device (current_context));
_gdk_event_emit (event);
gdk_event_free (event);
g_object_unref (current_context);
current_context = NULL;
}
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
NSPoint point = [sender draggingLocation];
NSPoint screen_point = [self convertBaseToScreen:point];
GdkEvent *event;
int gx, gy;
update_context_from_dragging_info (sender);
_gdk_quartz_window_nspoint_to_gdk_xy (screen_point, &gx, &gy);
event = gdk_event_new (GDK_DRAG_MOTION);
event->dnd.window = g_object_ref ([[self contentView] gdkWindow]);
event->dnd.send_event = FALSE;
event->dnd.context = g_object_ref (current_context);
event->dnd.time = GDK_CURRENT_TIME;
event->dnd.x_root = gx;
event->dnd.y_root = gy;
gdk_event_set_device (event, gdk_drag_context_get_device (current_context));
_gdk_event_emit (event);
gdk_event_free (event);
return drag_action_to_drag_operation (current_context->action);
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPoint point = [sender draggingLocation];
NSPoint screen_point = [self convertBaseToScreen:point];
GdkEvent *event;
int gy, gx;
update_context_from_dragging_info (sender);
_gdk_quartz_window_nspoint_to_gdk_xy (screen_point, &gx, &gy);
event = gdk_event_new (GDK_DROP_START);
event->dnd.window = g_object_ref ([[self contentView] gdkWindow]);
event->dnd.send_event = FALSE;
event->dnd.context = g_object_ref (current_context);
event->dnd.time = GDK_CURRENT_TIME;
event->dnd.x_root = gx;
event->dnd.y_root = gy;
gdk_event_set_device (event, gdk_drag_context_get_device (current_context));
_gdk_event_emit (event);
gdk_event_free (event);
g_object_unref (current_context);
current_context = NULL;
return YES;
}
- (BOOL)wantsPeriodicDraggingUpdates
{
return NO;
}
- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
{
GdkEvent *event;
g_assert (_gdk_quartz_drag_source_context != NULL);
event = gdk_event_new (GDK_DROP_FINISHED);
event->dnd.window = g_object_ref ([[self contentView] gdkWindow]);
event->dnd.send_event = FALSE;
event->dnd.context = g_object_ref (_gdk_quartz_drag_source_context);
gdk_event_set_device (event,
gdk_drag_context_get_device (_gdk_quartz_drag_source_context));
_gdk_event_emit (event);
gdk_event_free (event);
g_object_unref (_gdk_quartz_drag_source_context);
_gdk_quartz_drag_source_context = NULL;
}
@end