// @(#)root/graf2d:$Id$ // Author: Timur Pocheptsov 16/02/2012 /************************************************************************* * Copyright (C) 1995-2012, Rene Brun and Fons Rademakers. * * All rights reserved. * * * * For the licensing terms see $ROOTSYS/LICENSE. * * For the list of contributors see $ROOTSYS/README/CREDITS. * *************************************************************************/ //#define DEBUG_ROOT_COCOA //#define NDEBUG #ifdef DEBUG_ROOT_COCOA #include #include #include "TClass.h" #endif #include #include #include #include "ROOTOpenGLView.h" #include "QuartzWindow.h" #include "QuartzPixmap.h" #include "QuartzUtils.h" #include "CocoaUtils.h" #include "X11Colors.h" #include "X11Buffer.h" #include "X11Events.h" #include "TGWindow.h" #include "TGClient.h" #include "TSystem.h" #include "TGCocoa.h" namespace ROOT { namespace MacOSX { namespace X11 { //______________________________________________________________________________ QuartzWindow *CreateTopLevelWindow(Int_t x, Int_t y, UInt_t w, UInt_t h, UInt_t /*border*/, Int_t depth, UInt_t clss, void */*visual*/, SetWindowAttributes_t *attr, UInt_t) { NSRect winRect = {}; winRect.origin.x = GlobalXROOTToCocoa(x); winRect.origin.y = GlobalYROOTToCocoa(y + h); winRect.size.width = w; winRect.size.height = h; //TODO check mask. const NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask; // QuartzWindow * const newWindow = [[QuartzWindow alloc] initWithContentRect : winRect styleMask : styleMask backing : NSBackingStoreBuffered defer : YES windowAttributes : attr]; if (!newWindow) throw std::runtime_error("CreateTopLevelWindow failed"); // newWindow.fDepth = depth; newWindow.fClass = clss; return newWindow; } //______________________________________________________________________________ QuartzView *CreateChildView(QuartzView * /*parent*/, Int_t x, Int_t y, UInt_t w, UInt_t h, UInt_t /*border*/, Int_t /*depth*/, UInt_t /*clss*/, void * /*visual*/, SetWindowAttributes_t *attr, UInt_t /*wtype*/) { NSRect viewRect = {}; viewRect.origin.x = x; viewRect.origin.y = y; viewRect.size.width = w; viewRect.size.height = h; QuartzView * const view = [[QuartzView alloc] initWithFrame : viewRect windowAttributes : attr]; if (!view) throw std::runtime_error("CreateChildView failed"); return view; } //______________________________________________________________________________ void GetRootWindowAttributes(WindowAttributes_t *attr) { //'root' window does not exist, but we can request its attributes. assert(attr != 0 && "GetRootWindowAttributes, attr parameter is null"); NSArray * const screens = [NSScreen screens]; assert(screens != nil && "screens array is nil"); NSScreen * const mainScreen = [screens objectAtIndex : 0]; assert(mainScreen != nil && "screen with index 0 is nil"); *attr = WindowAttributes_t(); TGCocoa * const gCocoa = dynamic_cast(gVirtualX); assert(gCocoa != 0 && "GetRootWindowAttributes, gVirtualX is either null or has a wrong type"); const Rectangle &frame = gCocoa->GetDisplayGeometry(); attr->fX = 0; attr->fY = 0; attr->fWidth = frame.fWidth; attr->fHeight = frame.fHeight; attr->fBorderWidth = 0; attr->fYourEventMask = 0; attr->fAllEventMasks = 0;//??? attr->fDepth = NSBitsPerPixelFromDepth([mainScreen depth]); attr->fVisual = 0; attr->fRoot = 0; } //Coordinate conversion. //TODO: check how TGX11 extracts/changes window attributes. //______________________________________________________________________________ NSPoint ConvertPointFromBaseToScreen(NSWindow *window, NSPoint windowPoint) { assert(window != nil && "ConvertPointFromBaseToScreen, window parameter is nil"); //I have no idea why apple deprecated function for a point conversion and requires rect conversion, //point conversion seems to produce wrong results with HiDPI. NSRect tmpRect = {}; tmpRect.origin = windowPoint; tmpRect.size = CGSizeMake(1., 1.);//This is strange size :) But if they require rect, 0,0 - will not work? tmpRect = [window convertRectToScreen : tmpRect]; return tmpRect.origin; } //______________________________________________________________________________ NSPoint ConvertPointFromScreenToBase(NSPoint screenPoint, NSWindow *window) { assert(window != nil && "ConvertPointFromScreenToBase, window parameter is nil"); //I have no idea why apple deprecated function for a point conversion and requires rect conversion, //point conversion seems to produce wrong results with HiDPI. NSRect tmpRect = {}; tmpRect.origin = screenPoint; tmpRect.size = CGSizeMake(1., 1.); tmpRect = [window convertRectFromScreen : tmpRect]; return tmpRect.origin; } //______________________________________________________________________________ int GlobalYCocoaToROOT(CGFloat yCocoa) { //We can have several physical displays and thus several NSScreens in some arbitrary order. //With Cocoa, some screens can have negative coordinates - to the left ro down to the primary //screen (whatever it means). With X11 (XQuartz) though it's always 0,0. TGCocoa * gCocoa = dynamic_cast(gVirtualX); assert(gCocoa != 0 && "GlobalYCocoaToROOT, gVirtualX is either nul or has a wrong type"); const Rectangle frame = gCocoa->GetDisplayGeometry(); return int(frame.fHeight - (yCocoa - frame.fY)); } //______________________________________________________________________________ int GlobalXCocoaToROOT(CGFloat xCocoa) { TGCocoa * gCocoa = dynamic_cast(gVirtualX); assert(gCocoa != 0 && "GlobalXCocoaToROOT, gVirtualX is either nul or has a wrong type"); const Rectangle frame = gCocoa->GetDisplayGeometry(); //With X11 coordinate space always starts from 0, 0 return int(xCocoa - frame.fX); } //______________________________________________________________________________ int GlobalYROOTToCocoa(CGFloat yROOT) { TGCocoa * gCocoa = dynamic_cast(gVirtualX); assert(gCocoa != 0 && "GlobalYROOTToCocoa, gVirtualX is either nul or has a wrong type"); const Rectangle frame = gCocoa->GetDisplayGeometry(); return int(frame.fY + (frame.fHeight - yROOT)); } //______________________________________________________________________________ int GlobalXROOTToCocoa(CGFloat xROOT) { TGCocoa * gCocoa = dynamic_cast(gVirtualX); assert(gCocoa != 0 && "GlobalXROOTToCocoa, gVirtualX is either nul or has a wrong type"); const Rectangle frame = gCocoa->GetDisplayGeometry(); //With X11 coordinate space always starts from 0, 0 return int(frame.fX + xROOT); } //______________________________________________________________________________ int LocalYCocoaToROOT(NSView *parentView, CGFloat yCocoa) { assert(parentView != nil && "LocalYCocoaToROOT, parent view is nil"); return int(parentView.frame.size.height - yCocoa); } //______________________________________________________________________________ int LocalYROOTToCocoa(NSView *parentView, CGFloat yROOT) { //:) assert(parentView != nil && "LocalYROOTToCocoa, parent view is nil"); return int(parentView.frame.size.height - yROOT); } //______________________________________________________________________________ int LocalYROOTToCocoa(NSObject *drawable, CGFloat yROOT) { //:) assert(drawable != nil && "LocalYROOTToCocoa, drawable is nil"); return int(drawable.fHeight - yROOT); } //______________________________________________________________________________ NSPoint TranslateToScreen(NSView *from, NSPoint point) { assert(from != nil && "TranslateToScreen, 'from' parameter is nil"); const NSPoint winPoint = [from convertPoint : point toView : nil]; NSPoint screenPoint = ConvertPointFromBaseToScreen([from window], winPoint); screenPoint.x = GlobalXCocoaToROOT(screenPoint.x); screenPoint.y = GlobalYCocoaToROOT(screenPoint.y); return screenPoint; } //______________________________________________________________________________ NSPoint TranslateFromScreen(NSPoint point, NSView *to) { assert(to != nil && "TranslateFromScreen, 'to' parameter is nil"); point.x = GlobalXROOTToCocoa(point.x); point.y = GlobalYROOTToCocoa(point.y); point = ConvertPointFromScreenToBase(point, [to window]); return [to convertPoint : point fromView : nil]; } //______________________________________________________________________________ NSPoint TranslateCoordinates(NSView *from, NSView *to, NSPoint sourcePoint) { //Both views are valid. assert(from != nil && "TranslateCoordinates, 'from' parameter is nil"); assert(to != nil && "TranslateCoordinates, 'to' parameter is nil"); if ([from window] == [to window]) { //Both views are in the same window. return [to convertPoint : sourcePoint fromView : from]; } else { //May be, I can do it in one call, but it's not obvious for me //what is 'pixel aligned backing store coordinates' and //if they are the same as screen coordinates. //Many thanks to Apple for deprecated functions!!! const NSPoint win1Point = [from convertPoint : sourcePoint toView : nil]; const NSPoint screenPoint = ConvertPointFromBaseToScreen([from window], win1Point); const NSPoint win2Point = ConvertPointFromScreenToBase(screenPoint, [to window]); return [to convertPoint : win2Point fromView : nil]; } } //______________________________________________________________________________ bool ScreenPointIsInView(NSView *view, Int_t x, Int_t y) { assert(view != nil && "ScreenPointIsInView, view parameter is nil"); NSPoint point = {}; point.x = x, point.y = y; point = TranslateFromScreen(point, view); const NSRect viewFrame = view.frame; if (point.x < 0 || point.x > viewFrame.size.width) return false; if (point.y < 0 || point.y > viewFrame.size.height) return false; return true; } //______________________________________________________________________________ QuartzWindow *FindWindowInPoint(Int_t x, Int_t y) { const Util::AutoreleasePool pool;//array's counter is increased, all object in array are also retained. NSArray * const orderedWindows = [NSApp orderedWindows]; for (NSWindow *window in orderedWindows) { if (![window isKindOfClass : [QuartzWindow class]]) continue; QuartzWindow * const qw = (QuartzWindow *)window; if (qw.fIsDeleted)//Because of reference counting this can happen. continue; //Check if point is inside. if (ScreenPointIsInView(qw.fContentView, x, y)) return qw; } return nil; } //______________________________________________________________________________ NSView *FindDNDAwareViewInPoint(NSArray *children, Window_t dragWinID, Window_t inputWinID, Int_t x, Int_t y, Int_t maxDepth) { assert(children != nil && "FindDNDAwareViewInPoint, children parameter is nil"); if (maxDepth <= 0) return nil; NSEnumerator * const reverseEnumerator = [children reverseObjectEnumerator]; for (NSView *child in reverseEnumerator) { if (!ScreenPointIsInView(child, x, y)) continue; if (child.fIsDNDAware && child.fID != dragWinID && child.fID != inputWinID) return child;//got it! NSView * const testView = FindDNDAwareViewInPoint([child subviews], dragWinID, inputWinID, x, y, maxDepth - 1); if (testView) return testView; } return nil; } //______________________________________________________________________________ NSView *FindDNDAwareViewInPoint(NSView *parentView, Window_t dragWinID, Window_t inputWinID, Int_t x, Int_t y, Int_t maxDepth) { //X and Y are ROOT's screen coordinates (Y is inverted). if (maxDepth <= 0) return nil; const Util::AutoreleasePool pool; if (!parentView) {//Start from the screen as a 'root' window. NSArray * const orderedWindows = [NSApp orderedWindows]; for (NSWindow *window in orderedWindows) { if (![window isKindOfClass : [QuartzWindow class]]) continue; QuartzWindow * const qw = (QuartzWindow *)window; if (qw.fIsDeleted)//Because of reference counting this can happen. continue; if (qw.fMapState != kIsViewable) continue; //First, check this view itself, my be we found what we need already. NSView *testView = qw.fContentView; if (!ScreenPointIsInView(testView, x, y)) continue; if (testView.fIsDNDAware && testView.fID != dragWinID && testView.fID != inputWinID) return testView; //Recursive part, check children. NSArray * const children = [testView subviews]; testView = FindDNDAwareViewInPoint(children, dragWinID, inputWinID, x, y, maxDepth - 1); if (testView) return testView; } //We did not find anything for 'root' window as parent. return nil; } else { //Parent view is tested already (or should not be tested at all, check children. return FindDNDAwareViewInPoint([parentView subviews], dragWinID, inputWinID, x, y, maxDepth); } } //______________________________________________________________________________ QuartzWindow *FindWindowUnderPointer() { const Util::AutoreleasePool pool; NSArray * const orderedWindows = [NSApp orderedWindows]; for (NSWindow *nsWindow in orderedWindows) { if (![nsWindow isKindOfClass : [QuartzWindow class]]) continue; QuartzWindow * const qWindow = (QuartzWindow *)nsWindow; if (qWindow.fIsDeleted)//Because of reference counting this can happen. continue; if (qWindow.fMapState != kIsViewable)//Can it be false and still in this array??? continue; const NSPoint mousePosition = [qWindow mouseLocationOutsideOfEventStream]; const NSSize windowSize = qWindow.frame.size; if (mousePosition.x >= 0 && mousePosition.x <= windowSize.width && mousePosition.y >= 0 && mousePosition.y <= windowSize.height) return qWindow; } return nil; } //______________________________________________________________________________ NSView *FindViewUnderPointer() { //TODO: call FindViewInPoint using cursor screen coordiantes. const Util::AutoreleasePool pool; if (QuartzWindow *topLevel = FindWindowUnderPointer()) { const NSPoint mousePosition = [topLevel mouseLocationOutsideOfEventStream]; return (NSView *)[[topLevel contentView] hitTest : mousePosition]; } return nil; } //______________________________________________________________________________ QuartzWindow *FindWindowForPointerEvent(NSEvent *pointerEvent) { //FindWindowForPointerEvent is required because due to grabs the receiver of the event //can be different from the actual window under cursor. assert(pointerEvent != nil && "FindWindowForPointerEvent, pointerEvent parameter is nil"); const Util::AutoreleasePool pool; NSArray * const orderedWindows = [NSApp orderedWindows]; for (NSWindow *nsWindow in orderedWindows) { if (![nsWindow isKindOfClass : [QuartzWindow class]]) continue; QuartzWindow * const qWindow = (QuartzWindow *)nsWindow; if (qWindow.fIsDeleted)//Because of reference counting this can happen. continue; if (qWindow.fMapState != kIsViewable)//Can it be false and still in this array??? continue; NSPoint mousePosition = [pointerEvent locationInWindow]; //The event has a window, so position is in this window's coordinate system, //convert it into screen point first. if ([pointerEvent window]) { //convertBaseToScreen is deprecated. //mousePosition = [[pointerEvent window] convertBaseToScreen : mousePosition]; mousePosition = ConvertPointFromBaseToScreen([pointerEvent window], mousePosition); } //convertScreenToBase is deprecated. //mousePosition = [qWindow convertScreenToBase : mousePosition]; mousePosition = ConvertPointFromScreenToBase(mousePosition, qWindow); const NSSize windowSize = qWindow.frame.size; if (mousePosition.x >= 0 && mousePosition.x <= windowSize.width && mousePosition.y >= 0 && mousePosition.y <= windowSize.height) return qWindow; } return nil; } //______________________________________________________________________________ NSView *FindViewForPointerEvent(NSEvent *pointerEvent) { //FindViewForPointerEvent is required because of grabs - the receiver of the //event can be different from the actual window under cursor. assert(pointerEvent != nil && "FindViewForPointerEvent, pointerEvent parameter is nil"); const Util::AutoreleasePool pool; if (QuartzWindow *topLevel = FindWindowForPointerEvent(pointerEvent)) { NSPoint mousePosition = [pointerEvent locationInWindow]; if ([pointerEvent window]) mousePosition = ConvertPointFromBaseToScreen([pointerEvent window], mousePosition); //convertScreenToBase is deprecated. //mousePosition = [topLevel convertScreenToBase : mousePosition]; mousePosition = ConvertPointFromScreenToBase(mousePosition, topLevel); return (NSView *)[[topLevel contentView] hitTest : mousePosition]; } return nil; } //______________________________________________________________________________ void ClipToShapeMask(NSView *view, CGContextRef ctx) { assert(view != nil && "ClipToShapeMask, view parameter is nil"); assert(ctx != 0 && "ClipToShapeMask, ctx parameter is null"); QuartzWindow * const topLevelParent = view.fQuartzWindow; assert(topLevelParent.fShapeCombineMask != nil && "ClipToShapeMask, fShapeCombineMask is nil on a top-level window"); assert(topLevelParent.fShapeCombineMask.fImage != 0 && "ClipToShapeMask, shape mask is null"); //Important: shape mask should have the same width and height as //a top-level window. In ROOT it does not :( Say hello to visual artifacts. //Attach clip mask to the context. NSRect clipRect = view.frame; if (!view.fParentView) { //'view' is a top-level view. clipRect = CGRectMake(0, 0, topLevelParent.fShapeCombineMask.fWidth, topLevelParent.fShapeCombineMask.fHeight); CGContextClipToMask(ctx, clipRect, topLevelParent.fShapeCombineMask.fImage); } else { //More complex case: 'self' is a child view, we have to create a subimage from shape mask. clipRect.origin = [view.fParentView convertPoint : clipRect.origin toView : [view window].contentView]; clipRect.origin.y = X11::LocalYROOTToCocoa((NSView *)[view window].contentView, clipRect.origin.y + clipRect.size.height); if (AdjustCropArea(topLevelParent.fShapeCombineMask, clipRect)) { const Util::CFScopeGuard clipImageGuard(CGImageCreateWithImageInRect(topLevelParent.fShapeCombineMask.fImage, clipRect)); clipRect.origin = CGPointZero; CGContextClipToMask(ctx, clipRect, clipImageGuard.Get()); } else { //View is invisible. CGRect rect = {}; CGContextClipToRect(ctx, rect); } } } //______________________________________________________________________________ void SetWindowAttributes(const SetWindowAttributes_t *attr, NSObject *window) { assert(attr != 0 && "SetWindowAttributes, attr parameter is null"); assert(window != nil && "SetWindowAttributes, window parameter is nil"); const Mask_t mask = attr->fMask; if (mask & kWABackPixel) window.fBackgroundPixel = attr->fBackgroundPixel; if (mask & kWAEventMask) window.fEventMask = attr->fEventMask; if (mask & kWABitGravity) window.fBitGravity = attr->fBitGravity; if (mask & kWAWinGravity) window.fWinGravity = attr->fWinGravity; //TODO: More attributes to set - //cursor for example, etc. if (mask & kWAOverrideRedirect) { //This is quite a special case. //TODO: Must be checked yet, if I understand this correctly! if ([(NSObject *)window isKindOfClass : [QuartzWindow class]]) { QuartzWindow * const qw = (QuartzWindow *)window; [qw setStyleMask : NSBorderlessWindowMask]; [qw setAlphaValue : 0.95]; } window.fOverrideRedirect = YES; } } //______________________________________________________________________________ void GetWindowGeometry(NSObject *win, WindowAttributes_t *dst) { assert(win != nil && "GetWindowGeometry, win parameter is nil"); assert(dst != 0 && "GetWindowGeometry, dst paremeter is null"); dst->fX = win.fX; dst->fY = win.fY; dst->fWidth = win.fWidth; dst->fHeight = win.fHeight; } //______________________________________________________________________________ void GetWindowAttributes(NSObject *window, WindowAttributes_t *dst) { assert(window != nil && "GetWindowAttributes, window parameter is nil"); assert(dst != 0 && "GetWindowAttributes, attr parameter is null"); *dst = WindowAttributes_t(); //fX, fY, fWidth, fHeight. GetWindowGeometry(window, dst); //Actually, most of them are not used by GUI. dst->fBorderWidth = 0; dst->fDepth = window.fDepth; //Dummy value. dst->fVisual = 0; //Dummy value. dst->fRoot = 0; dst->fClass = window.fClass; dst->fBitGravity = window.fBitGravity; dst->fWinGravity = window.fWinGravity; //Dummy value. dst->fBackingStore = kAlways;//??? CHECK dst->fBackingPlanes = 0; //Dummy value. dst->fBackingPixel = 0; dst->fSaveUnder = 0; //Dummy value. dst->fColormap = 0; //Dummy value. dst->fMapInstalled = kTRUE; dst->fMapState = window.fMapState; dst->fAllEventMasks = window.fEventMask; dst->fYourEventMask = window.fEventMask; //Not used by GUI. //dst->fDoNotPropagateMask dst->fOverrideRedirect = window.fOverrideRedirect; //Dummy value. dst->fScreen = 0; } //With Apple's poor man's objective C/C++ + "brilliant" Cocoa you never know, what should be //the linkage of callback functions, API + language dialects == MESS. I declare/define this comparators here //as having "C++" linkage. If one good day clang will start to complane, I'll have to change this. //______________________________________________________________________________ NSComparisonResult CompareViewsToLower(id view1, id view2, void *context) { id topView = (id)context;//ARC will require _brigde cast, but NO ARC! :) if (view1 == topView) return NSOrderedAscending; if (view2 == topView) return NSOrderedDescending; return NSOrderedSame; } //______________________________________________________________________________ NSComparisonResult CompareViewsToRaise(id view1, id view2, void *context) { id topView = (id)context; if (view1 == topView) return NSOrderedDescending; if (view2 == topView) return NSOrderedAscending; return NSOrderedSame; } //______________________________________________________________________________ NSPoint GetCursorHotStop(NSImage *image, ECursor cursor) { assert(image != nil && "CursroHotSpot, image parameter is nil"); const NSSize imageSize = image.size; if (cursor == kArrowRight) return CGPointMake(imageSize.width, imageSize.height / 2); return CGPointMake(imageSize.width / 2, imageSize.height / 2); } //TGTextView/TGHtml is a very special window: it's a TGCompositeFrame, //which has TGCompositeFrame inside (TGViewFrame). This TGViewFrame //delegates Expose events to its parent, and parent tries to draw //inside a TGViewFrame. This does not work with default //QuartzView -drawRect/TGCocoa. So I need a trick to identify //this special window. //TODO: possibly refactor these functions in a more generic way - not //to have two separate versions for text and html. //______________________________________________________________________________ bool ViewIsTextView(unsigned viewID) { const TGWindow * const window = gClient->GetWindowById(viewID); if (!window) return false; return window->InheritsFrom("TGTextView"); } //______________________________________________________________________________ bool ViewIsTextView(NSView *view) { assert(view != nil && "ViewIsTextView, view parameter is nil"); return ViewIsTextView(view.fID); } //______________________________________________________________________________ bool ViewIsTextViewFrame(NSView *view, bool checkParent) { assert(view != nil && "ViewIsTextViewFrame, view parameter is nil"); const TGWindow * const window = gClient->GetWindowById(view.fID); if (!window) return false; if (!window->InheritsFrom("TGViewFrame")) return false; if (!checkParent) return true; if (!view.fParentView) return false; return ViewIsTextView(view.fParentView); } //______________________________________________________________________________ bool ViewIsHtmlView(unsigned viewID) { const TGWindow * const window = gClient->GetWindowById(viewID); if (!window) return false; return window->InheritsFrom("TGHtml"); } //______________________________________________________________________________ bool ViewIsHtmlView(NSView *view) { assert(view != nil && "ViewIsHtmlView, view parameter is nil"); return ViewIsHtmlView(view.fID); } //______________________________________________________________________________ bool ViewIsHtmlViewFrame(NSView *view, bool checkParent) { // assert(view != nil && "ViewIsHtmlViewFrame, view parameter is nil"); const TGWindow * const window = gClient->GetWindowById(view.fID); if (!window) return false; if (!window->InheritsFrom("TGViewFrame")) return false; if (!checkParent) return true; if (!view.fParentView) return false; return ViewIsHtmlView(view.fParentView); } //______________________________________________________________________________ NSView *FrameForTextView(NSView *textView) { assert(textView != nil && "FrameForTextView, textView parameter is nil"); for (NSView *child in [textView subviews]) { if (ViewIsTextViewFrame(child, false)) return child; } return nil; } //______________________________________________________________________________ NSView *FrameForHtmlView(NSView *htmlView) { assert(htmlView != nil && "FrameForHtmlView, htmlView parameter is nil"); for (NSView *child in [htmlView subviews]) { if (ViewIsHtmlViewFrame(child, false)) return child; } return nil; } //______________________________________________________________________________ bool LockFocus(NSView *view) { assert(view != nil && "LockFocus, view parameter is nil"); assert([view isKindOfClass : [QuartzView class]] && "LockFocus, QuartzView is expected"); if ([view lockFocusIfCanDraw]) { NSGraphicsContext *nsContext = [NSGraphicsContext currentContext]; assert(nsContext != nil && "LockFocus, currentContext is nil"); CGContextRef currContext = (CGContextRef)[nsContext graphicsPort]; assert(currContext != 0 && "LockFocus, graphicsPort is null");//remove this assert? ((QuartzView *)view).fContext = currContext; return true; } return false; } //______________________________________________________________________________ void UnlockFocus(NSView *view) { assert(view != nil && "UnlockFocus, view parameter is nil"); assert([view isKindOfClass : [QuartzView class]] && "UnlockFocus, QuartzView is expected"); [view unlockFocus]; ((QuartzView *)view).fContext = 0; } }//X11 }//MacOSX }//ROOT namespace Quartz = ROOT::Quartz; namespace Util = ROOT::MacOSX::Util; namespace X11 = ROOT::MacOSX::X11; #ifdef DEBUG_ROOT_COCOA namespace { //______________________________________________________________________________ void log_attributes(const SetWindowAttributes_t *attr, unsigned winID) { //This function is loggin requests, at the moment I can not set all //of these attributes, so I first have to check, what is actually //requested by ROOT. static std::ofstream logfile("win_attr.txt"); const Mask_t mask = attr->fMask; if (mask & kWABackPixmap) logfile<<"win "< *) src area : (X11::Rectangle) area withMask : (QuartzImage *)mask clipOrigin : (X11::Point) clipXY toPoint : (X11::Point) dstPoint { assert(fContentView != nil && "copy:area:toPoint:, fContentView is nil"); [fContentView copy : src area : area withMask : mask clipOrigin : clipXY toPoint : dstPoint]; } //______________________________________________________________________________ - (unsigned char *) readColorBits : (X11::Rectangle) area { assert(fContentView != nil && "readColorBits:, fContentView is nil"); return [fContentView readColorBits : area]; } //X11Window protocol's implementation. //______________________________________________________________________________ - (QuartzView *) fParentView { return nil; } //______________________________________________________________________________ - (void) setFParentView : (QuartzView *) parent { (void)parent; } //______________________________________________________________________________ - (NSView *) fContentView { return fContentView; } //______________________________________________________________________________ - (QuartzWindow *) fQuartzWindow { return self; } ///////////////////////////////////////////////////////////// //SetWindowAttributes_t/WindowAttributes_t. //... forwards to fContentView. //______________________________________________________________________________ - (void) setFBackgroundPixel : (unsigned long) backgroundColor { assert(fContentView != nil && "setFBackgroundPixel, fContentView is nil"); if (!fShapeCombineMask) { CGFloat rgba[] = {0., 0., 0., 1.}; X11::PixelToRGB(backgroundColor, rgba); [self setBackgroundColor : [NSColor colorWithColorSpace : [NSColorSpace deviceRGBColorSpace] components : rgba count : 4]]; } fContentView.fBackgroundPixel = backgroundColor; } //______________________________________________________________________________ - (unsigned long) fBackgroundPixel { assert(fContentView != nil && "fBackgroundPixel, fContentView is nil"); return fContentView.fBackgroundPixel; } //______________________________________________________________________________ - (int) fMapState { //Top-level window can be only kIsViewable or kIsUnmapped (not unviewable). assert(fContentView != nil && "fMapState, content view is nil"); if ([fContentView isHidden]) return kIsUnmapped; return kIsViewable; } //______________________________________________________________________________ - (void) addChild : (NSView *) child { assert(child != nil && "addChild, child view is nil"); if (!fContentView) { //This can happen only in case of re-parent operation. assert([child isKindOfClass : [QuartzView class]] && "addChild: gl view in a top-level window as content view is not supported"); fContentView = (QuartzView *)child; [self setContentView : child]; fContentView.fParentView = nil; } else [fContentView addChild : child]; } //______________________________________________________________________________ - (void) getAttributes : (WindowAttributes_t *) attr { assert(fContentView != 0 && "getAttributes, content view is nil"); assert(attr && "getAttributes, attr parameter is nil"); X11::GetWindowAttributes(self, attr); } //______________________________________________________________________________ - (void) setAttributes : (const SetWindowAttributes_t *)attr { assert(attr != 0 && "setAttributes, attr parameter is null"); #ifdef DEBUG_ROOT_COCOA log_attributes(attr, self.fID); #endif X11::SetWindowAttributes(attr, self); } //______________________________________________________________________________ - (void) mapRaised { assert(fContentView && "mapRaised, content view is nil"); const Util::AutoreleasePool pool; [fContentView setHidden : NO]; [self makeKeyAndOrderFront : self]; [fContentView configureNotifyTree]; if (fDelayedTransient) { fDelayedTransient = NO; [fMainWindow addChildWindow : self ordered : NSWindowAbove]; } } //______________________________________________________________________________ - (void) mapWindow { assert(fContentView != nil && "mapWindow, content view is nil"); const Util::AutoreleasePool pool; // [self orderFront : self]; [fContentView setHidden : NO]; [self makeKeyAndOrderFront : self]; [fContentView configureNotifyTree]; if (fDelayedTransient) { fDelayedTransient = NO; [fMainWindow addChildWindow : self ordered : NSWindowAbove]; } } //______________________________________________________________________________ - (void) mapSubwindows { assert(fContentView != nil && "mapSubwindows, content view is nil"); const Util::AutoreleasePool pool; [fContentView mapSubwindows]; [fContentView configureNotifyTree]; } //______________________________________________________________________________ - (void) unmapWindow { assert(fContentView != nil && "unmapWindow, content view is nil"); [fContentView setHidden : YES]; [self orderOut : self]; if (fMainWindow && !fDelayedTransient) { [fMainWindow removeChildWindow : self]; fMainWindow = nil; } } //Events. //______________________________________________________________________________ - (void) sendEvent : (NSEvent *) theEvent { assert(fContentView != nil && "sendEvent, content view is nil"); if (theEvent.type == NSLeftMouseDown || theEvent.type == NSRightMouseDown) { bool generateFakeRelease = false; const NSPoint windowPoint = [theEvent locationInWindow]; if (windowPoint.x <= 4 || windowPoint.x >= self.fWidth - 4) generateFakeRelease = true; if (windowPoint.y <= 4 || windowPoint.y >= self.fHeight - 4) generateFakeRelease = true; //convertPointFromBase is deprectated. //const NSPoint viewPoint = [fContentView convertPointFromBase : windowPoint]; const NSPoint viewPoint = [fContentView convertPoint : windowPoint fromView : nil]; if (viewPoint.y <= 0 && windowPoint.y >= 0) generateFakeRelease = true; TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "sendEvent, gVirtualX is either null or not of TGCocoa type"); if (vx->GetEventTranslator()->HasPointerGrab() && generateFakeRelease) { vx->GetEventTranslator()->GenerateButtonReleaseEvent(fContentView, theEvent, theEvent.type == NSLeftMouseDown ? kButton1 : kButton3);//yes, button release??? //Yes, ignore this event completely (this means, you are not able to immediately start //resizing a window, if some popup is open. Actually, this is more or less //the same as with X11.app and X11 version. return; } } //Always call default version. [super sendEvent : theEvent]; } //NSWindowDelegate's methods. //______________________________________________________________________________ - (BOOL) windowShouldClose : (id) sender { (void)sender; assert(fContentView != nil && "windowShouldClose, content view is nil"); //TODO: check this!!! Children are //transient windows and ROOT does not handle //such a deletion properly, noop then. if ([[self childWindows] count]) return NO; //Prepare client message for a window. Event_t closeEvent = {}; closeEvent.fWindow = fContentView.fID; closeEvent.fType = kClientMessage; closeEvent.fFormat = 32;//Taken from GUI classes. closeEvent.fHandle = TGCocoa::fgDeleteWindowAtom; closeEvent.fUser[0] = TGCocoa::fgDeleteWindowAtom; //Place it into the queue. TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "windowShouldClose, gVirtualX is either null or has a type different from TGCocoa"); vx->SendEvent(fContentView.fID, &closeEvent); //Do not let AppKit to close a window. return NO; } //______________________________________________________________________________ - (void) windowDidBecomeKey : (NSNotification *) aNotification { assert(fContentView != nil && "windowDidBecomeKey, fContentView is nil"); (void) aNotification; if (!fContentView.fOverrideRedirect) { TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "becomeFirstResponder, gVirtualX is null or not of TGCocoa type"); vx->GetEventTranslator()->GenerateFocusChangeEvent(self.fContentView); } } /* //______________________________________________________________________________ - (void) windowDidResignKey : (NSNotification *) aNotification { // TGCocoa * const vx = dynamic_cast(gVirtualX); // assert(vx != 0 && "becomeFirstResponder, gVirtualX is null or not of TGCocoa type"); // vx->GetEventTranslator()->GenerateFocusChangeEvent(nil); } */ /* //______________________________________________________________________________ -(BOOL)canBecomeKeyWindow { assert(fContentView != nil && "canBecomeKeyWindow, fContentView is nil"); return !fContentView.fOverrideRedirect; } */ @end // // // // //Passive key grab info. @implementation PassiveKeyGrab { unichar fKeyCode; NSUInteger fModifiers; } //______________________________________________________________________________ - (id) initWithKey : (unichar) keyCode modifiers : (NSUInteger) modifiers { if (self = [super init]) { fKeyCode = keyCode; fModifiers = modifiers; } return self; } //______________________________________________________________________________ - (BOOL) matchKey : (unichar) keyCode modifiers : (NSUInteger) modifiers { return keyCode == fKeyCode && modifiers == fModifiers; } //______________________________________________________________________________ - (BOOL) matchKey : (unichar) keyCode { return keyCode == fKeyCode; } //______________________________________________________________________________ - (unichar) fKeyCode { return fKeyCode; } //______________________________________________________________________________ - (NSUInteger) fModifiers { return fModifiers; } @end // // //X11 "property". @interface QuartzWindowProperty : NSObject { NSData *fPropertyData; Atom_t fType; unsigned fFormat; } @property (nonatomic, readonly) Atom_t fType; @end @implementation QuartzWindowProperty @synthesize fType; //______________________________________________________________________________ - (id) initWithData : (unsigned char *) data size : (unsigned) dataSize type : (Atom_t) type format : (unsigned) format { if (self = [super init]) { //Memory is zero-initialized, but just to make it explicit: fPropertyData = nil; fType = 0; fFormat = 0; [self resetPropertyData : data size : dataSize type : type format : format]; } return self; } //______________________________________________________________________________ - (void) dealloc { [fPropertyData release]; [super dealloc]; } //______________________________________________________________________________ - (void) resetPropertyData : (unsigned char *) data size : (unsigned) dataSize type : (Atom_t) type format : (unsigned) format { [fPropertyData release]; fFormat = format; if (format == 16) dataSize *= 2; else if (format == 32) dataSize *= 4; fPropertyData = [[NSData dataWithBytes : data length : dataSize] retain]; fType = type; } //______________________________________________________________________________ - (NSData *) fPropertyData { return fPropertyData; } //______________________________________________________________________________ - (unsigned) fFormat { return fFormat; } @end @implementation QuartzView { QuartzPixmap *fBackBuffer; NSMutableArray *fPassiveKeyGrabs; BOOL fIsOverlapped; NSMutableDictionary *fX11Properties; QuartzImage *fBackgroundPixmap; X11::PointerGrab fCurrentGrabType; unsigned fActiveGrabEventMask; BOOL fActiveGrabOwnerEvents; } @synthesize fID; @synthesize fContext; ///////////////////// //SetWindowAttributes_t/WindowAttributes_t @synthesize fEventMask; @synthesize fClass; @synthesize fDepth; @synthesize fBitGravity; @synthesize fWinGravity; @synthesize fBackgroundPixel; @synthesize fOverrideRedirect; //SetWindowAttributes_t/WindowAttributes_t ///////////////////// @synthesize fParentView; @synthesize fPassiveGrabButton; @synthesize fPassiveGrabEventMask; @synthesize fPassiveGrabKeyModifiers; @synthesize fActiveGrabEventMask; @synthesize fPassiveGrabOwnerEvents; @synthesize fSnapshotDraw; @synthesize fCurrentCursor; @synthesize fIsDNDAware; //______________________________________________________________________________ - (id) initWithFrame : (NSRect) frame windowAttributes : (const SetWindowAttributes_t *)attr { if (self = [super initWithFrame : frame]) { //Make this explicit (though memory is zero initialized). fBackBuffer = nil; fID = 0; //Passive grab parameters. fPassiveGrabButton = -1;//0 is kAnyButton. fPassiveGrabEventMask = 0; fPassiveGrabOwnerEvents = NO; fPassiveKeyGrabs = [[NSMutableArray alloc] init]; [self setCanDrawConcurrently : NO]; [self setHidden : YES]; //Actually, check if view need this. // if (attr) X11::SetWindowAttributes(attr, self); fCurrentCursor = kPointer; fX11Properties = [[NSMutableDictionary alloc] init]; fCurrentGrabType = X11::kPGNoGrab; fActiveGrabEventMask = 0; fActiveGrabOwnerEvents = YES; } return self; } //______________________________________________________________________________ - (void) dealloc { [fBackBuffer release]; [fPassiveKeyGrabs release]; [fX11Properties release]; [fBackgroundPixmap release]; [super dealloc]; } //______________________________________________________________________________ - (void)updateTrackingAreas { if (!fID) return; if (NSIsEmptyRect([self visibleRect])) return; const Util::AutoreleasePool pool; if (NSArray *trackingArray = [self trackingAreas]) { const NSUInteger size = [trackingArray count]; for (NSUInteger i = 0; i < size; ++i) { NSTrackingArea * const t = [trackingArray objectAtIndex : i]; [self removeTrackingArea : t]; } } const NSUInteger trackerOptions = NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp | NSTrackingInVisibleRect | NSTrackingEnabledDuringMouseDrag; NSRect frame = {}; frame.size.width = self.fWidth; frame.size.height = self.fHeight; NSTrackingArea * const tracker = [[NSTrackingArea alloc] initWithRect : frame options : trackerOptions owner : self userInfo : nil]; [self addTrackingArea : tracker]; [tracker release]; } //______________________________________________________________________________ - (void) updateTrackingAreasAfterRaise { [self updateTrackingAreas]; for (QuartzView *childView in [self subviews]) [childView updateTrackingAreasAfterRaise]; } //X11Drawable protocol. //______________________________________________________________________________ - (BOOL) fIsPixmap { return NO; } //______________________________________________________________________________ - (BOOL) fIsOpenGLWidget { return NO; } //______________________________________________________________________________ - (int) fX { return self.frame.origin.x; } //______________________________________________________________________________ - (int) fY { return self.frame.origin.y; } //______________________________________________________________________________ - (unsigned) fWidth { return self.frame.size.width; } //______________________________________________________________________________ - (unsigned) fHeight { return self.frame.size.height; } //______________________________________________________________________________ - (void) setDrawableSize : (NSSize) newSize { assert(!(newSize.width < 0) && "setDrawableSize, width is negative"); assert(!(newSize.height < 0) && "setDrawableSize, height is negative"); //This will cause redraw(?) //In X11, resize changes the size, but upper-left corner is not changed. //In Cocoa, bottom-left is fixed. NSRect frame = self.frame; frame.size = newSize; self.frame = frame; } //______________________________________________________________________________ - (void) setX : (int) x Y : (int) y width : (unsigned) w height : (unsigned) h { NSRect newFrame = {}; newFrame.origin.x = x; newFrame.origin.y = y; newFrame.size.width = w; newFrame.size.height = h; self.frame = newFrame; } //______________________________________________________________________________ - (void) setX : (int) x Y : (int) y { NSRect newFrame = self.frame; newFrame.origin.x = x; newFrame.origin.y = y; self.frame = newFrame; } //______________________________________________________________________________ - (void) copyImage : (QuartzImage *) srcImage area : (X11::Rectangle) area withMask : (QuartzImage *) mask clipOrigin : (X11::Point) clipXY toPoint : (X11::Point) dstPoint { //TODO: test and fix all possible cases with crop area and masks. //Check parameters. assert(srcImage != nil && "copyImage:area:withMask:clipOrigin:toPoint:, srcImage parameter is nil"); assert(srcImage.fImage != nil && "copyImage:area:withMask:clipOrigin:toPoint:, srcImage.fImage is nil"); //Check self. assert(self.fContext != 0 && "copyImage:area:withMask:clipOrigin:toPoint:, self.fContext is null"); if (!X11::AdjustCropArea(srcImage, area)) { NSLog(@"QuartzView: -copyImage:area:withMask:clipOrigin:toPoint:, srcRect and copyRect do not intersect"); return; } //No RAII for subImage, since it can be really subimage or image itself and //in these cases there is no need to release image. CGImageRef subImage = 0; bool needSubImage = false; if (area.fX || area.fY || area.fWidth != srcImage.fWidth || area.fHeight != srcImage.fHeight) { needSubImage = true; subImage = X11::CreateSubImage(srcImage, area); if (!subImage) { NSLog(@"QuartzView: -copyImage:area:withMask:clipOrigin:toPoint:, subimage creation failed"); return; } } else subImage = srcImage.fImage; //Save context state. const Quartz::CGStateGuard ctxGuard(self.fContext); //Scale and translate to undo isFlipped. CGContextTranslateCTM(self.fContext, 0., self.fHeight); CGContextScaleCTM(self.fContext, 1., -1.); //Set clip mask on a context. if (mask) { assert(mask.fImage != nil && "copyImage:area:withMask:clipOrigin:toPoint:, mask.fImage is nil"); assert(CGImageIsMask(mask.fImage) == true && "copyImage:area:withMask:clipOrigin:toPoint:, mask.fImage is not a mask"); //clipXY.fY = X11::LocalYROOTToCocoa(self, clipXY.fY + mask.fHeight); const CGFloat clipY = X11::LocalYROOTToCocoa(self, CGFloat(clipXY.fY) + mask.fHeight); //const CGRect clipRect = CGRectMake(clipXY.fX, clipXY.fY, mask.fWidth, mask.fHeight); const CGRect clipRect = CGRectMake(clipXY.fX, clipY, mask.fWidth, mask.fHeight); CGContextClipToMask(self.fContext, clipRect, mask.fImage); } //Convert from X11 to Cocoa (as soon as we scaled y * -1). //dstPoint.fY = X11::LocalYROOTToCocoa(self, dstPoint.fY + area.fHeight); const CGFloat dstY = X11::LocalYROOTToCocoa(self, CGFloat(dstPoint.fY) + area.fHeight); //const CGRect imageRect = CGRectMake(dstPoint.fX, dstPoint.fY, area.fWidth, area.fHeight); const CGRect imageRect = CGRectMake(dstPoint.fX, dstY, area.fWidth, area.fHeight); CGContextDrawImage(self.fContext, imageRect, subImage); if (needSubImage) CGImageRelease(subImage); } //______________________________________________________________________________ - (void) copyView : (QuartzView *) srcView area : (X11::Rectangle) area toPoint : (X11::Point) dstPoint { //To copy one "window" to another "window", I have to ask source QuartzView to draw intself into //bitmap, and copy this bitmap into the destination view. //TODO: this code must be tested, with all possible cases. assert(srcView != nil && "copyView:area:toPoint:, srcView parameter is nil"); const NSRect frame = [srcView frame]; //imageRep is in autorelease pool now. NSBitmapImageRep * const imageRep = [srcView bitmapImageRepForCachingDisplayInRect : frame]; if (!imageRep) { NSLog(@"QuartzView: -copyView:area:toPoint failed"); return; } assert(srcView != nil && "copyView:area:toPoint:, srcView parameter is nil"); assert(self.fContext != 0 && "copyView:area:toPoint, self.fContext is null"); //It can happen, that src and self are the same. //cacheDisplayInRect calls drawRect with bitmap context //(and this will reset self.fContext: I have to save/restore it. CGContextRef ctx = srcView.fContext; srcView.fSnapshotDraw = YES; [srcView cacheDisplayInRect : frame toBitmapImageRep : imageRep]; srcView.fSnapshotDraw = NO; srcView.fContext = ctx; const CGRect subImageRect = CGRectMake(area.fX, area.fY, area.fWidth, area.fHeight); const Util::CFScopeGuard subImage(CGImageCreateWithImageInRect(imageRep.CGImage, subImageRect)); if (!subImage.Get()) { NSLog(@"QuartzView: -copyView:area:toPoint, CGImageCreateWithImageInRect failed"); return; } const Quartz::CGStateGuard ctxGuard(self.fContext); const CGRect imageRect = CGRectMake(dstPoint.fX, [self visibleRect].size.height - (CGFloat(dstPoint.fY) + area.fHeight), area.fWidth, area.fHeight); CGContextTranslateCTM(self.fContext, 0., [self visibleRect].size.height); CGContextScaleCTM(self.fContext, 1., -1.); CGContextDrawImage(self.fContext, imageRect, subImage.Get()); } //______________________________________________________________________________ - (void) copyPixmap : (QuartzPixmap *) srcPixmap area : (X11::Rectangle) area withMask : (QuartzImage *) mask clipOrigin : (X11::Point) clipXY toPoint : (X11::Point) dstPoint { //Check parameters. assert(srcPixmap != nil && "copyPixmap:area:withMask:clipOrigin:toPoint:, srcPixmap parameter is nil"); if (!X11::AdjustCropArea(srcPixmap, area)) { NSLog(@"QuartzView: -copyPixmap:area:withMask:clipOrigin:toPoint, no intersection between pixmap rectangle and cropArea"); return; } //Check self. assert(self.fContext != 0 && "copyPixmap:area:withMask:clipOrigin:toPoint:, self.fContext is null"); //Save context state. const Quartz::CGStateGuard ctxGuard(self.fContext); CGContextTranslateCTM(self.fContext, 0., self.frame.size.height);//??? CGContextScaleCTM(self.fContext, 1., -1.); const Util::CFScopeGuard imageFromPixmap([srcPixmap createImageFromPixmap]); assert(imageFromPixmap.Get() != 0 && "copyPixmap:area:withMask:clipOrigin:toPoint:, createImageFromPixmap failed"); CGImageRef subImage = 0; bool needSubImage = false; if (area.fX || area.fY || area.fWidth != srcPixmap.fWidth || area.fHeight != srcPixmap.fHeight) { needSubImage = true; const CGRect subImageRect = CGRectMake(area.fX, area.fY, area.fHeight, area.fWidth); subImage = CGImageCreateWithImageInRect(imageFromPixmap.Get(), subImageRect); if (!subImage) { NSLog(@"QuartzView: -copyImage:area:withMask:clipOrigin:toPoint:, subimage creation failed"); return; } } else subImage = imageFromPixmap.Get(); if (mask) { //TODO: testtesttest!!! assert(mask.fImage != nil && "copyPixmap:area:withMask:clipOrigin:toPoint:, mask.fImage is nil"); assert(CGImageIsMask(mask.fImage) == true && "copyPixmap:area:withMask:clipOrigin:toPoint:, mask.fImage is not a mask"); //clipXY.fY = X11::LocalYROOTToCocoa(self, clipXY.fY + mask.fHeight); const CGFloat clipY = X11::LocalYROOTToCocoa(self, CGFloat(clipXY.fY) + mask.fHeight); //const CGRect clipRect = CGRectMake(clipXY.fX, clipXY.fY, mask.fWidth, mask.fHeight); const CGRect clipRect = CGRectMake(clipXY.fX, clipY, mask.fWidth, mask.fHeight); CGContextClipToMask(self.fContext, clipRect, mask.fImage); } //dstPoint.fY = X11::LocalYCocoaToROOT(self, dstPoint.fY + area.fHeight); const CGFloat dstY = X11::LocalYCocoaToROOT(self, CGFloat(dstPoint.fY) + area.fHeight); const CGRect imageRect = CGRectMake(dstPoint.fX, dstY, area.fWidth, area.fHeight); CGContextDrawImage(self.fContext, imageRect, imageFromPixmap.Get()); if (needSubImage) CGImageRelease(subImage); } //______________________________________________________________________________ - (void) copyImage : (QuartzImage *) srcImage area : (X11::Rectangle) area toPoint : (X11::Point) dstPoint { assert(srcImage != nil && "copyImage:area:toPoint:, srcImage parameter is nil"); assert(srcImage.fImage != nil && "copyImage:area:toPoint:, srcImage.fImage is nil"); assert(self.fContext != 0 && "copyImage:area:toPoint:, fContext is null"); if (!X11::AdjustCropArea(srcImage, area)) { NSLog(@"QuartzView: -copyImage:area:toPoint, image and copy area do not intersect"); return; } CGImageRef subImage = 0; bool needSubImage = false; if (area.fX || area.fY || area.fWidth != srcImage.fWidth || area.fHeight != srcImage.fHeight) { needSubImage = true; subImage = X11::CreateSubImage(srcImage, area); if (!subImage) { NSLog(@"QuartzView: -copyImage:area:toPoint:, subimage creation failed"); return; } } else subImage = srcImage.fImage; const Quartz::CGStateGuard ctxGuard(self.fContext); CGContextTranslateCTM(self.fContext, 0., self.fHeight); CGContextScaleCTM(self.fContext, 1., -1.); //dstPoint.fY = X11::LocalYCocoaToROOT(self, dstPoint.fY + area.fHeight); const CGFloat dstY = X11::LocalYCocoaToROOT(self, CGFloat(dstPoint.fY) + area.fHeight); //const CGRect imageRect = CGRectMake(dstPoint.fX, dstPoint.fY, area.fWidth, area.fHeight); const CGRect imageRect = CGRectMake(dstPoint.fX, dstY, area.fWidth, area.fHeight); CGContextDrawImage(self.fContext, imageRect, subImage); if (needSubImage) CGImageRelease(subImage); } //______________________________________________________________________________ - (void) copy : (NSObject *) src area : (X11::Rectangle) area withMask : (QuartzImage *)mask clipOrigin : (X11::Point) clipXY toPoint : (X11::Point) dstPoint { assert(src != nil && "copy:area:withMask:clipOrigin:toPoint:, src parameter is nil"); assert(area.fWidth && area.fHeight && "copy:area:withMask:clipOrigin:toPoint:, area to copy is empty"); if ([src isKindOfClass : [QuartzWindow class]]) { //Forget about mask (can I have it???) QuartzWindow * const qw = (QuartzWindow *)src; //Will not work with OpenGL. [self copyView : (QuartzView *)qw.fContentView area : area toPoint : dstPoint]; } else if ([src isKindOfClass : [QuartzView class]]) { //Forget about mask (can I have it???) [self copyView : (QuartzView *)src area : area toPoint : dstPoint]; } else if ([src isKindOfClass : [QuartzPixmap class]]) { [self copyPixmap : (QuartzPixmap *)src area : area withMask : mask clipOrigin : clipXY toPoint : dstPoint]; } else if ([src isKindOfClass : [QuartzImage class]]) { [self copyImage : (QuartzImage *)src area : area withMask : mask clipOrigin : clipXY toPoint : dstPoint]; } else { assert(0 && "copy:area:withMask:clipOrigin:toPoint:, src is of unknown type"); } } //______________________________________________________________________________ - (unsigned char *) readColorBits : (X11::Rectangle) area { // assert(area.fWidth && area.fHeight && "readColorBits:, area to copy is empty"); const NSRect visRect = [self visibleRect]; const X11::Rectangle srcRect(int(visRect.origin.x), int(visRect.origin.y), unsigned(visRect.size.width), unsigned(visRect.size.height)); if (!X11::AdjustCropArea(srcRect, area)) { NSLog(@"QuartzView: -readColorBits:, visible rect of view and copy area do not intersect"); return 0; } NSBitmapImageRep * const imageRep = [self bitmapImageRepForCachingDisplayInRect : visRect];//imageRect is autoreleased. if (!imageRep) { NSLog(@"QuartzView: -readColorBits:, bitmapImageRepForCachingDisplayInRect failed"); return 0; } CGContextRef ctx = self.fContext; //Save old context if any. [self cacheDisplayInRect : visRect toBitmapImageRep : imageRep]; self.fContext = ctx; //Restore old context. // const unsigned char *srcData = [imageRep bitmapData]; //We have a source data now. Let's allocate buffer for ROOT's GUI and convert source data. unsigned char *data = 0; try { data = new unsigned char[area.fWidth * area.fHeight * 4];//bgra? } catch (const std::bad_alloc &) { NSLog(@"QuartzView: -readColorBits:, memory allocation failed"); return 0; } const NSInteger bitsPerPixel = [imageRep bitsPerPixel]; //TODO: ohhh :((( assert(bitsPerPixel == 32 && "-readColorBits:, no alpha channel???"); const NSInteger bytesPerRow = [imageRep bytesPerRow]; const unsigned dataWidth = bytesPerRow / (bitsPerPixel / 8);//assume an octet :( unsigned char *dstPixel = data; const unsigned char *line = srcData + area.fY * dataWidth * 4; const unsigned char *srcPixel = line + area.fX * 4; for (unsigned i = 0; i < area.fHeight; ++i) { for (unsigned j = 0; j < area.fWidth; ++j, srcPixel += 4, dstPixel += 4) { dstPixel[0] = srcPixel[2]; dstPixel[1] = srcPixel[1]; dstPixel[2] = srcPixel[0]; dstPixel[3] = srcPixel[3]; } line += dataWidth * 4; srcPixel = line + area.fX * 4; } return data; } //______________________________________________________________________________ - (void) setFBackgroundPixmap : (QuartzImage *) pixmap { if (fBackgroundPixmap != pixmap) { [fBackgroundPixmap release]; if (pixmap) fBackgroundPixmap = [pixmap retain]; else fBackgroundPixmap = nil; } } //______________________________________________________________________________ - (QuartzImage *) fBackgroundPixmap { //I do not autorelease, screw this idiom! return fBackgroundPixmap; } //______________________________________________________________________________ - (int) fMapState { if ([self isHidden]) return kIsUnmapped; for (QuartzView *parent = fParentView; parent; parent = parent.fParentView) { if ([parent isHidden]) return kIsUnviewable; } return kIsViewable; } //______________________________________________________________________________ - (QuartzPixmap *) fBackBuffer { return fBackBuffer;//No autorelease, I know the object's lifetime myself. } //______________________________________________________________________________ - (void) setFBackBuffer : (QuartzPixmap *) backBuffer { if (fBackBuffer != backBuffer) { [fBackBuffer release]; if (backBuffer) fBackBuffer = [backBuffer retain]; else fBackBuffer = nil; } } //______________________________________________________________________________ - (NSView *) fContentView { return self; } //______________________________________________________________________________ - (QuartzWindow *) fQuartzWindow { return (QuartzWindow *)[self window]; } //______________________________________________________________________________ - (void) activatePassiveGrab { fCurrentGrabType = X11::kPGPassiveGrab; } //______________________________________________________________________________ - (void) activateImplicitGrab { fCurrentGrabType = X11::kPGImplicitGrab; } //______________________________________________________________________________ - (void) activateGrab : (unsigned) eventMask ownerEvents : (BOOL) ownerEvents { fCurrentGrabType = X11::kPGActiveGrab; fActiveGrabEventMask = eventMask; fActiveGrabOwnerEvents = ownerEvents; } //______________________________________________________________________________ - (void) cancelGrab { fCurrentGrabType = X11::kPGNoGrab; fActiveGrabEventMask = 0; fActiveGrabOwnerEvents = YES; } //______________________________________________________________________________ - (BOOL) acceptsCrossingEvents : (unsigned) eventMask { bool accepts = fEventMask & eventMask; if (fCurrentGrabType == X11::kPGPassiveGrab)//In ROOT passive grabs are always with owner_events == true. accepts = accepts || (fPassiveGrabEventMask & eventMask); if (fCurrentGrabType == X11::kPGActiveGrab) { if (fActiveGrabOwnerEvents) accepts = accepts || (fActiveGrabOwnerEvents & eventMask); else accepts = fActiveGrabOwnerEvents & eventMask; } return accepts; } //______________________________________________________________________________ - (void) addChild : (NSView *) child { assert(child != nil && "addChild, child view is nil"); [self addSubview : child]; child.fParentView = self; } //______________________________________________________________________________ - (void) getAttributes : (WindowAttributes_t *)attr { assert(attr != 0 && "getAttributes, attr parameter is null"); X11::GetWindowAttributes(self, attr); } //______________________________________________________________________________ - (void) setAttributes : (const SetWindowAttributes_t *)attr { assert(attr != 0 && "setAttributes, attr parameter is null"); #ifdef DEBUG_ROOT_COCOA log_attributes(attr, fID); #endif X11::SetWindowAttributes(attr, self); } //______________________________________________________________________________ - (void) mapRaised { //Move view to the top of subviews (in UIKit there is a special method). QuartzView * const parent = fParentView; [self removeFromSuperview]; [parent addSubview : self]; [self setHidden : NO]; } //______________________________________________________________________________ - (void) mapWindow { [self setHidden : NO]; } //______________________________________________________________________________ - (void) mapSubwindows { for (QuartzView * v in [self subviews]) { [v setHidden : NO]; //[v mapSubwindows]; } } //______________________________________________________________________________ - (void) unmapWindow { [self setHidden : YES]; } //______________________________________________________________________________ - (BOOL) fIsOverlapped { return fIsOverlapped; } //______________________________________________________________________________ - (void) setOverlapped : (BOOL) overlap { fIsOverlapped = overlap; for (NSView *child in [self subviews]) [child setOverlapped : overlap]; } //______________________________________________________________________________ - (void) raiseWindow { using namespace X11;//Comparators. for (QuartzView *sibling in [fParentView subviews]) { if (self == sibling) continue; if ([sibling isHidden]) continue; //TODO: equal test is not good :) I have a baaad feeling about this ;) if (CGRectEqualToRect(sibling.frame, self.frame)) { [sibling setOverlapped : YES]; // [sibling setHidden : YES]; // } } [self setOverlapped : NO]; // [self setHidden : NO]; // [fParentView sortSubviewsUsingFunction : CompareViewsToRaise context : (void *)self]; // [self updateTrackingAreasAfterRaise]; // [self setNeedsDisplay : YES];//? } //______________________________________________________________________________ - (void) lowerWindow { using namespace X11; NSEnumerator * const reverseEnumerator = [[fParentView subviews] reverseObjectEnumerator]; for (QuartzView *sibling in reverseEnumerator) { if (sibling == self) continue; //TODO: equal test is not good :) I have a baaad feeling about this ;) if (CGRectEqualToRect(sibling.frame, self.frame)) { [sibling setOverlapped : NO]; // [sibling setHidden : NO]; // [sibling setNeedsDisplay : YES]; [self setOverlapped : YES]; // [self setHidden : YES]; // break; } } [fParentView sortSubviewsUsingFunction : CompareViewsToLower context : (void*)self];//NO ARC! No __bridge! } //______________________________________________________________________________ - (BOOL) isFlipped { //Now view's placement, geometry, moving and resizing can be //done with ROOT's (X11) coordinates without conversion. return YES; } //______________________________________________________________________________ - (void) configureNotifyTree { if (self.fMapState == kIsViewable || fIsOverlapped == YES) { if (fEventMask & kStructureNotifyMask) { TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx && "configureNotifyTree, gVirtualX is either null or has type different from TGCocoa"); vx->GetEventTranslator()->GenerateConfigureNotifyEvent(self, self.frame); } for (NSView *v in [self subviews]) [v configureNotifyTree]; } } //Key grabs. //______________________________________________________________________________ - (void) addPassiveKeyGrab : (unichar) keyCode modifiers : (NSUInteger) modifiers { //TODO: why am I removing the old one??? :) [self removePassiveKeyGrab : keyCode modifiers : modifiers]; PassiveKeyGrab * const newGrab = [[PassiveKeyGrab alloc] initWithKey : keyCode modifiers : modifiers]; [fPassiveKeyGrabs addObject : newGrab]; [newGrab release]; } //______________________________________________________________________________ - (void) removePassiveKeyGrab : (unichar) keyCode modifiers : (NSUInteger) modifiers { const NSUInteger count = [fPassiveKeyGrabs count]; for (NSUInteger i = 0; i < count; ++i) { PassiveKeyGrab *grab = [fPassiveKeyGrabs objectAtIndex : i]; if ([grab matchKey : keyCode modifiers : modifiers]) { [fPassiveKeyGrabs removeObjectAtIndex : i]; break; } } } //______________________________________________________________________________ - (PassiveKeyGrab *) findPassiveKeyGrab : (unichar) keyCode modifiers : (NSUInteger) modifiers { NSEnumerator * const enumerator = [fPassiveKeyGrabs objectEnumerator]; while (PassiveKeyGrab *grab = (PassiveKeyGrab *)[enumerator nextObject]) { if ([grab matchKey : keyCode modifiers : modifiers]) return grab; } return nil; } //______________________________________________________________________________ - (PassiveKeyGrab *) findPassiveKeyGrab : (unichar) keyCode { //Do not check modifiers. NSEnumerator * const enumerator = [fPassiveKeyGrabs objectEnumerator]; while (PassiveKeyGrab *grab = (PassiveKeyGrab *)[enumerator nextObject]) { if ([grab matchKey : keyCode]) return grab; } return nil; } //Painting mechanics. //______________________________________________________________________________ - (void) drawRect : (NSRect) dirtyRect { using namespace X11; (void)dirtyRect; if (fID) { if (TGWindow * const window = gClient->GetWindowById(fID)) { if (ViewIsTextViewFrame(self, true) ||ViewIsHtmlViewFrame(self, true))//It's never painted, parent renders child. true == check the parent also. return; NSGraphicsContext * const nsContext = [NSGraphicsContext currentContext]; assert(nsContext != nil && "drawRect, currentContext returned nil"); TGCocoa * const vx = (TGCocoa *)gVirtualX; vx->CocoaDrawON(); fContext = (CGContextRef)[nsContext graphicsPort]; assert(fContext != 0 && "drawRect, graphicsPort returned null"); const Quartz::CGStateGuard ctxGuard(fContext); //Non-rectangular windows. if (self.fQuartzWindow.fShapeCombineMask) X11::ClipToShapeMask(self, fContext); if (window->InheritsFrom("TGContainer"))//It always has an ExposureMask. vx->GetEventTranslator()->GenerateExposeEvent(self, [self visibleRect]); if (fEventMask & kExposureMask) { if (ViewIsTextView(self)) { //Send Expose event, using child view (this is how it's done in GUI :( ). NSView * const viewFrame = FrameForTextView(self); if (viewFrame) vx->GetEventTranslator()->GenerateExposeEvent(viewFrame, [viewFrame visibleRect]);//Now we set fExposedRegion for TGView. } if (ViewIsHtmlView(self)) { //TODO: remove code duplication here. NSView *const viewFrame = FrameForHtmlView(self); if (viewFrame) vx->GetEventTranslator()->GenerateExposeEvent(viewFrame, [viewFrame visibleRect]); } //Ask ROOT's widget/window to draw itself. gClient->NeedRedraw(window, kTRUE); if (!fSnapshotDraw && !ViewIsTextView(self) && !ViewIsHtmlView(self)) { //If Cocoa repaints widget, cancel all ROOT's "outside of paint event" //rendering into this widget ... Except it's a text view :) gClient->CancelRedraw(window); vx->GetCommandBuffer()->RemoveGraphicsOperationsForWindow(fID); } } if (fBackBuffer) { //Very "special" window. const X11::Rectangle copyArea(0, 0, fBackBuffer.fWidth, fBackBuffer.fHeight); [self copy : fBackBuffer area : copyArea withMask : nil clipOrigin : X11::Point() toPoint : X11::Point()]; } vx->CocoaDrawOFF(); #ifdef DEBUG_ROOT_COCOA CGContextSetRGBStrokeColor(fContext, 1., 0., 0., 1.); CGContextStrokeRect(fContext, dirtyRect); #endif fContext = 0; } else { #ifdef DEBUG_ROOT_COCOA NSLog(@"QuartzView: -drawRect method, no window for id %u was found", fID); #endif } } } //Event handling. //______________________________________________________________________________ - (void) setFrame : (NSRect) newFrame { //In case of TBrowser, setFrame started infinite recursion: //HandleConfigure for embedded main frame emits signal, slot //calls layout, layout calls setFrame -> HandleConfigure and etc. etc. if (CGRectEqualToRect(newFrame, self.frame)) return; [super setFrame : newFrame]; } //______________________________________________________________________________ - (void) setFrameSize : (NSSize) newSize { //Check, if setFrameSize calls setFrame. [super setFrameSize : newSize]; if ((fEventMask & kStructureNotifyMask) && (self.fMapState == kIsViewable || fIsOverlapped == YES)) { TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "setFrameSize:, gVirtualX is either null or has a type, different from TGCocoa"); vx->GetEventTranslator()->GenerateConfigureNotifyEvent(self, self.frame); } [self setNeedsDisplay : YES];//? } //______________________________________________________________________________ - (void) mouseDown : (NSEvent *) theEvent { assert(fID != 0 && "mouseDown, fID is 0"); TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "mouseDown, gVirtualX is either null or has a type, different from TGCocoa"); vx->GetEventTranslator()->GenerateButtonPressEvent(self, theEvent, kButton1); } //______________________________________________________________________________ - (void) scrollWheel : (NSEvent*) theEvent { assert(fID != 0 && "scrollWheel, fID is 0"); TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "scrollWheel, gVirtualX is either null or has a type, different from TGCocoa"); const CGFloat deltaY = [theEvent deltaY]; if (deltaY < 0) { vx->GetEventTranslator()->GenerateButtonPressEvent(self, theEvent, kButton5); vx->GetEventTranslator()->GenerateButtonReleaseEvent(self, theEvent, kButton5); } else if (deltaY > 0) { vx->GetEventTranslator()->GenerateButtonPressEvent(self, theEvent, kButton4); vx->GetEventTranslator()->GenerateButtonReleaseEvent(self, theEvent, kButton4); } } #ifdef DEBUG_ROOT_COCOA //______________________________________________________________________________ - (void) printViewInformation { assert(fID != 0 && "printWindowInformation, fID is 0"); const TGWindow * const window = gClient->GetWindowById(fID); assert(window != 0 && "printWindowInformation, window not found"); NSLog(@"-----------------View %u info:---------------------", fID); NSLog(@"ROOT's window class is %s", window->IsA()->GetName()); NSLog(@"event mask is:"); print_mask_info(fEventMask); NSLog(@"grab mask is:"); print_mask_info(fPassiveGrabEventMask); NSLog(@"view's geometry: x == %g, y == %g, w == %g, h == %g", self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height); NSLog(@"----------------End of view info------------------"); } #endif //______________________________________________________________________________ - (void) rightMouseDown : (NSEvent *) theEvent { assert(fID != 0 && "rightMouseDown, fID is 0"); #ifdef DEBUG_ROOT_COCOA [self printViewInformation]; #endif TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "rightMouseDown, gVirtualX is either null or has type different from TGCocoa"); vx->GetEventTranslator()->GenerateButtonPressEvent(self, theEvent, kButton3); } //______________________________________________________________________________ - (void) otherMouseDown : (NSEvent *) theEvent { assert(fID != 0 && "otherMouseDown, fID is 0"); //Funny enough, [theEvent buttonNumber] is not the same thing as button masked in [NSEvent pressedMouseButtons], //button number actually is a kind of right operand for bitshift for pressedMouseButtons. if ([theEvent buttonNumber] == 2) {//this '2' will correspond to '4' in pressedMouseButtons. //I do not care about mouse buttons after left/right/wheel - ROOT does not have //any code for this. TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "otherMouseDown, gVirtualX is either null or has type different from TGCocoa"); vx->GetEventTranslator()->GenerateButtonPressEvent(self, theEvent, kButton2); } } //______________________________________________________________________________ - (void) mouseUp : (NSEvent *) theEvent { assert(fID != 0 && "mouseUp, fID is 0"); TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx && "mouseUp, gVirtualX is either null or has type different from TGCocoa"); vx->GetEventTranslator()->GenerateButtonReleaseEvent(self, theEvent, kButton1); } //______________________________________________________________________________ - (void) rightMouseUp : (NSEvent *) theEvent { assert(fID != 0 && "rightMouseUp, fID is 0"); TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "rightMouseUp, gVirtualX is either null or has type different from TGCocoa"); vx->GetEventTranslator()->GenerateButtonReleaseEvent(self, theEvent, kButton3); } //______________________________________________________________________________ - (void) otherMouseUp : (NSEvent *) theEvent { assert(fID != 0 && "otherMouseUp, fID is 0"); //Here I assume it's always kButton2. TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "otherMouseUp, gVirtualX is either null or has type different from TGCocoa"); vx->GetEventTranslator()->GenerateButtonReleaseEvent(self, theEvent, kButton2); } //______________________________________________________________________________ - (void) mouseEntered : (NSEvent *) theEvent { assert(fID != 0 && "mouseEntered, fID is 0"); TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "mouseEntered, gVirtualX is null or not of TGCocoa type"); vx->GetEventTranslator()->GenerateCrossingEvent(theEvent); } //______________________________________________________________________________ - (void) mouseExited : (NSEvent *) theEvent { assert(fID != 0 && "mouseExited, fID is 0"); TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "mouseExited, gVirtualX is null or not of TGCocoa type"); vx->GetEventTranslator()->GenerateCrossingEvent(theEvent); } //______________________________________________________________________________ - (void) mouseMoved : (NSEvent *) theEvent { assert(fID != 0 && "mouseMoved, fID is 0"); if (fParentView)//Suppress events in all views, except the top-level one. return; //TODO: check, that it does not create additional problems. TGCocoa *vx = dynamic_cast(gVirtualX); assert(vx != 0 && "mouseMoved, gVirtualX is null or not of TGCocoa type"); vx->GetEventTranslator()->GeneratePointerMotionEvent(theEvent); } //______________________________________________________________________________ - (void) mouseDragged : (NSEvent *)theEvent { assert(fID != 0 && "mouseDragged, fID is 0"); TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "mouseDragged, gVirtualX is null or not of TGCocoa type"); vx->GetEventTranslator()->GeneratePointerMotionEvent(theEvent); } //______________________________________________________________________________ - (void) rightMouseDragged : (NSEvent *)theEvent { assert(fID != 0 && "rightMouseDragged, fID is 0"); TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "rightMouseDragged, gVirtualX is null or not of TGCocoa type"); vx->GetEventTranslator()->GeneratePointerMotionEvent(theEvent); } //______________________________________________________________________________ - (void) otherMouseDragged : (NSEvent *)theEvent { assert(fID != 0 && "otherMouseDragged, fID is 0"); if ([theEvent buttonNumber] == 2) { TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "otherMouseDragged, gVirtualX is null or not of TGCocoa type"); vx->GetEventTranslator()->GeneratePointerMotionEvent(theEvent); } } //______________________________________________________________________________ - (void) keyDown:(NSEvent *)theEvent { assert(fID != 0 && "keyDown, fID is 0"); TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "keyDown, gVirtualX is null or not of TGCocoa type"); vx->GetEventTranslator()->GenerateKeyPressEvent(self, theEvent); } //______________________________________________________________________________ - (void) keyUp : (NSEvent *) theEvent { assert(fID != 0 && "keyUp, fID is 0"); TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "keyUp, gVirtualX is null or not of TGCocoa type"); vx->GetEventTranslator()->GenerateKeyReleaseEvent(self, theEvent); } //First responder staff. //______________________________________________________________________________ - (BOOL) acceptsFirstMouse : (NSEvent *)theEvent { (void)theEvent; return YES; } //______________________________________________________________________________ - (BOOL) acceptsFirstResponder { return YES; } /* //______________________________________________________________________________ - (BOOL) becomeFirstResponder { if (!fOverrideRedirect) { //Change focus. NSView *focusView = nil; for (NSView *view = self; view; view = view.fParentView) { if (view.fEventMask & kFocusChangeMask) { focusView = view; break; } } if (!focusView) focusView = ((QuartzWindow *)[self window]).fContentView; TGCocoa * const vx = dynamic_cast(gVirtualX); assert(vx != 0 && "becomeFirstResponder, gVirtualX is null or not of TGCocoa type"); vx->GetEventTranslator()->GenerateFocusChangeEvent(focusView); } return YES; } //______________________________________________________________________________ - (BOOL) resignFirstResponder { //Change focus. //NSResponder returns YES, so do I. return YES; } */ //Cursors. //______________________________________________________________________________ - (void) setFCurrentCursor : (ECursor) cursor { if (cursor != fCurrentCursor) { fCurrentCursor = cursor; [self.fQuartzWindow invalidateCursorRectsForView : self]; } } //______________________________________________________________________________ - (NSCursor *) createCustomCursor { const char *pngFileName = 0; switch (fCurrentCursor) { case kMove: pngFileName = "move_cursor.png"; break; case kArrowHor: pngFileName = "hor_arrow_cursor.png"; break; case kArrowVer: pngFileName = "ver_arrow_cursor.png"; break; case kArrowRight: pngFileName = "right_arrow_cursor.png"; break; case kRotate: pngFileName = "rotate.png"; break; case kBottomLeft: case kTopRight: pngFileName = "top_right_cursor.png"; break; case kTopLeft: case kBottomRight: pngFileName = "top_left_cursor.png"; break; default:; } if (pngFileName) { const char * const path = gSystem->Which("$ROOTSYS/icons", pngFileName, kReadPermission);//This must be deleted. const Util::ScopedArray arrayGuard(path); if (!path || path[0] == 0) { //File was not found. return nil; } NSString *nsPath = [NSString stringWithFormat : @"%s", path];//in autorelease pool. NSImage * const cursorImage = [[NSImage alloc] initWithContentsOfFile : nsPath];//must call release. if (!cursorImage) return nil; NSPoint hotSpot = X11::GetCursorHotStop(cursorImage, fCurrentCursor); NSCursor * const customCursor = [[[NSCursor alloc] initWithImage : cursorImage hotSpot : hotSpot] autorelease];//in autorelease pool. [cursorImage release]; return customCursor; } return nil; } //______________________________________________________________________________ - (void) resetCursorRects { //Cursors from TVirtaulX: // kBottomLeft, kBottomRight, kTopLeft, kTopRight, // kBottomSide, kLeftSide, kTopSide, kRightSide, // kMove, kCross, kArrowHor, kArrowVer, // kHand, kRotate, kPointer, kArrowRight, // kCaret, kWatch NSCursor *cursor = nil; switch (fCurrentCursor) { case kCross: cursor = [NSCursor crosshairCursor]; break; case kPointer: //Use simple arrow (or this special cursor will be even on GUI widgets). break; case kHand: cursor = [NSCursor openHandCursor]; break; case kLeftSide: cursor = [NSCursor resizeLeftCursor]; break; case kRightSide: cursor = [NSCursor resizeRightCursor]; break; case kTopSide: cursor = [NSCursor resizeUpCursor]; break; case kBottomSide: cursor = [NSCursor resizeDownCursor]; break; case kCaret: cursor = [NSCursor IBeamCursor]; break; case kRotate: case kWatch: default: cursor = [self createCustomCursor]; } if (cursor) [self addCursorRect : self.visibleRect cursor : cursor]; else [super resetCursorRects]; } //______________________________________________________________________________ - (void) setProperty : (const char *) propName data : (unsigned char *) propData size : (unsigned) dataSize forType : (Atom_t) dataType format : (unsigned) format { assert(propName != 0 && "setProperty:data:size:forType:, propName parameter is null"); assert(propData != 0 && "setProperty:data:size:forType:, propData parameter is null"); assert(dataSize != 0 && "setProperty:data:size:forType:, dataSize parameter is 0"); NSString * const key = [NSString stringWithCString : propName encoding : NSASCIIStringEncoding]; QuartzWindowProperty * property = (QuartzWindowProperty *)[fX11Properties valueForKey : key]; //At the moment (and I think this will never change) TGX11 always calls XChangeProperty with PropModeReplace. if (property) [property resetPropertyData : propData size : dataSize type : dataType format : format]; else { //No property found, add a new one. property = [[QuartzWindowProperty alloc] initWithData : propData size : dataSize type : dataType format : format]; [fX11Properties setObject : property forKey : key]; [property release]; } } //______________________________________________________________________________ - (BOOL) hasProperty : (const char *) propName { assert(propName != 0 && "hasProperty, propName parameter is null"); NSString * const key = [NSString stringWithCString : propName encoding : NSASCIIStringEncoding]; QuartzWindowProperty * const property = (QuartzWindowProperty *)[fX11Properties valueForKey : key]; return property != nil; } //______________________________________________________________________________ - (unsigned char *) getProperty : (const char *) propName returnType : (Atom_t *) type returnFormat : (unsigned *) format nElements : (unsigned *) nElements { assert(propName != 0 && "getProperty:returnType:returnFormat:nElements, propName parameter is null"); assert(type != 0 && "getProperty:returnType:returnFormat:nElements, type parameter is null"); assert(format != 0 && "getProperty:returnType:returnFormat:nElements, format parameter is null"); assert(nElements != 0 && "getProperty:returnType:returnFormat:nElements, nElements parameter is null"); NSString * const key = [NSString stringWithCString : propName encoding : NSASCIIStringEncoding]; QuartzWindowProperty * const property = (QuartzWindowProperty *)[fX11Properties valueForKey : key]; assert(property != 0 && "getProperty:returnType:returnFormat:nElements, property not found"); NSData * const propData = property.fPropertyData; const NSUInteger dataSize = [propData length]; unsigned char *buff = 0; try { buff = new unsigned char[dataSize](); } catch (const std::bad_alloc &) { NSLog(@"QuartzWindow: -getProperty:returnType:returnFormat:nElements, memory allocation failed");//Hmm, can I log, if new failed? :) return 0; } [propData getBytes : buff length : dataSize]; *format = property.fFormat; *nElements = dataSize; if (*format == 16) *nElements= dataSize / 2; else if (*format == 32) *nElements = dataSize / 4; *type = property.fType; return buff; } //______________________________________________________________________________ - (void) removeProperty : (const char *) propName { assert(propName != 0 && "removeProperty:, propName parameter is null"); NSString * const key = [NSString stringWithCString : propName encoding : NSASCIIStringEncoding]; [fX11Properties removeObjectForKey : key]; } //DND //______________________________________________________________________________ - (NSDragOperation) draggingEntered : (id) sender { NSPasteboard * const pasteBoard = [sender draggingPasteboard]; const NSDragOperation sourceDragMask = [sender draggingSourceOperationMask]; if ([[pasteBoard types] containsObject : NSFilenamesPboardType] && (sourceDragMask & NSDragOperationCopy)) return NSDragOperationCopy; return NSDragOperationNone; } //______________________________________________________________________________ - (BOOL) performDragOperation : (id) sender { //We can drag some files (images, pdfs, source code files) from //finder to ROOT's window (mainly TCanvas or text editor). //The logic is totally screwed here :((( - ROOT will try to //read a property of some window (not 'self', unfortunately) - //this works since on Window all data is in a global clipboard //(on X11 it simply does not work at all). //I'm attaching the file name as a property for the top level window, //there is no other way to make this data accessible for ROOT. NSPasteboard * const pasteBoard = [sender draggingPasteboard]; const NSDragOperation sourceDragMask = [sender draggingSourceOperationMask]; if ([[pasteBoard types] containsObject : NSFilenamesPboardType] && (sourceDragMask & NSDragOperationCopy)) { //Here I try to put string ("file://....") into window's property to make //it accesible from ROOT's GUI. const Atom_t textUriAtom = gVirtualX->InternAtom("text/uri-list", kFALSE); NSArray * const files = [pasteBoard propertyListForType : NSFilenamesPboardType]; for (NSString *path in files) { //ROOT can not process several files, use the first one. NSString * const item = [@"file://" stringByAppendingString : path]; //Yes, only ASCII encoding, but after all, ROOT's not able to work with NON-ASCII strings. const NSUInteger len = [item lengthOfBytesUsingEncoding : NSASCIIStringEncoding] + 1; try { std::vector propertyData(len); [item getCString : (char *)&propertyData[0] maxLength : propertyData.size() encoding : NSASCIIStringEncoding]; //There is no any guarantee, that this will ever work, logic in TGDNDManager is totally crazy. NSView * const targetView = self.fQuartzWindow.fContentView; [targetView setProperty : "_XC_DND_DATA" data : &propertyData[0] size : propertyData.size() forType : textUriAtom format : 8]; } catch (const std::bad_alloc &) { NSLog(@"QuartzView: -performDragOperation, memory allocation failed");//Hehe, can I log something in case of bad_alloc??? ;) return NO; } break; } //Property is attached now. //Gdk on windows creates three events on file drop (WM_DROPFILES): XdndEnter, XdndPosition, XdndDrop. //1. Dnd enter. Event_t event1 = {}; event1.fType = kClientMessage; event1.fWindow = fID; event1.fHandle = gVirtualX->InternAtom("XdndEnter", kFALSE); event1.fUser[0] = long(fID); event1.fUser[2] = textUriAtom;//gVirtualX->InternAtom("text/uri-list", kFALSE); // gVirtualX->SendEvent(fID, &event1); //2. Dnd position. Event_t event2 = {}; event2.fType = kClientMessage; event2.fWindow = fID; event2.fHandle = gVirtualX->InternAtom("XdndPosition", kFALSE); event2.fUser[0] = long(fID); event2.fUser[2] = 0;//Here I have to pack x and y for drop coordinates, shifting by 16 bits. NSPoint dropPoint = [sender draggingLocation]; //convertPointFromBase is deprecated. //dropPoint = [self convertPointFromBase : dropPoint]; dropPoint = [self convertPoint : dropPoint fromView : nil]; // dropPoint = X11::TranslateToScreen(self, dropPoint); event2.fUser[2] = UShort_t(dropPoint.y) | (UShort_t(dropPoint.x) << 16); gVirtualX->SendEvent(fID, &event2); Event_t event3 = {}; event3.fType = kClientMessage; event3.fWindow = fID; event3.fHandle = gVirtualX->InternAtom("XdndDrop", kFALSE); gVirtualX->SendEvent(fID, &event3); } return YES;//Always ok, even if file type is not supported - no need in "animation". } @end