/* * MACDRV Cocoa status item class * * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc. * * 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.1 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #import <Cocoa/Cocoa.h> #include "macdrv_cocoa.h" #import "cocoa_app.h" #import "cocoa_event.h" @interface WineStatusItem : NSView { NSStatusItem* item; WineEventQueue* queue; NSTrackingArea* trackingArea; NSImage* image; } @property (retain, nonatomic) NSStatusItem* item; @property (assign, nonatomic) WineEventQueue* queue; @property (retain, nonatomic) NSImage* image; @end @implementation WineStatusItem @synthesize item, queue, image; - (id) initWithEventQueue:(WineEventQueue*)inQueue { NSStatusBar* statusBar = [NSStatusBar systemStatusBar]; CGFloat thickness = [statusBar thickness]; self = [super initWithFrame:NSMakeRect(0, 0, thickness, thickness)]; if (self) { item = [[statusBar statusItemWithLength:NSSquareStatusItemLength] retain]; // This is a retain cycle which is broken in -removeFromStatusBar. [item setView:self]; queue = inQueue; trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect owner:self userInfo:nil]; [self addTrackingArea:trackingArea]; } return self; } - (void) dealloc { if (item) { NSStatusBar* statusBar = [NSStatusBar systemStatusBar]; [statusBar removeStatusItem:item]; [item release]; } [image release]; [trackingArea release]; [super dealloc]; } - (void) setImage:(NSImage*)inImage { if (image != inImage) { [image release]; image = [inImage retain]; [self setNeedsDisplay:YES]; } } - (void) removeFromStatusBar { if (item) { NSStatusBar* statusBar = [NSStatusBar systemStatusBar]; [statusBar removeStatusItem:item]; [item setView:nil]; [queue discardEventsPassingTest:^BOOL (macdrv_event* event){ return ((event->type == STATUS_ITEM_MOUSE_BUTTON && event->status_item_mouse_button.item == (macdrv_status_item)self) || (event->type == STATUS_ITEM_MOUSE_MOVE && event->status_item_mouse_move.item == (macdrv_status_item)self)); }]; self.item = nil; } } - (void) postMouseButtonEvent:(NSEvent*)nsevent; { macdrv_event* event; NSUInteger typeMask = NSEventMaskFromType([nsevent type]); CGPoint point = CGEventGetLocation([nsevent CGEvent]); point = cgpoint_win_from_mac(point); event = macdrv_create_event(STATUS_ITEM_MOUSE_BUTTON, nil); event->status_item_mouse_button.item = (macdrv_status_item)self; event->status_item_mouse_button.button = [nsevent buttonNumber]; event->status_item_mouse_button.down = (typeMask & (NSLeftMouseDownMask | NSRightMouseDownMask | NSOtherMouseDownMask)) != 0; event->status_item_mouse_button.count = [nsevent clickCount]; event->status_item_mouse_button.x = floor(point.x); event->status_item_mouse_button.y = floor(point.y); [queue postEvent:event]; macdrv_release_event(event); } /* * ---------- NSView methods ---------- */ - (void) drawRect:(NSRect)rect { [item drawStatusBarBackgroundInRect:[self bounds] withHighlight:NO]; if (image) { NSSize imageSize = [image size]; NSRect bounds = [self bounds]; NSPoint imageOrigin = NSMakePoint(NSMidX(bounds) - imageSize.width / 2, NSMidY(bounds) - imageSize.height / 2); imageOrigin = [self convertPointToBase:imageOrigin]; imageOrigin.x = floor(imageOrigin.x); imageOrigin.y = floor(imageOrigin.y); imageOrigin = [self convertPointFromBase:imageOrigin]; [image drawAtPoint:imageOrigin fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1]; } } /* * ---------- NSResponder methods ---------- */ - (void) mouseDown:(NSEvent*)event { [self postMouseButtonEvent:event]; } - (void) mouseDragged:(NSEvent*)event { [self mouseMoved:event]; } - (void) mouseMoved:(NSEvent*)nsevent { macdrv_event* event; CGPoint point = CGEventGetLocation([nsevent CGEvent]); point = cgpoint_win_from_mac(point); event = macdrv_create_event(STATUS_ITEM_MOUSE_MOVE, nil); event->status_item_mouse_move.item = (macdrv_status_item)self; event->status_item_mouse_move.x = floor(point.x); event->status_item_mouse_move.y = floor(point.y); [queue postEvent:event]; macdrv_release_event(event); } - (void) mouseUp:(NSEvent*)event { [self postMouseButtonEvent:event]; } - (void) otherMouseDown:(NSEvent*)event { [self postMouseButtonEvent:event]; } - (void) otherMouseDragged:(NSEvent*)event { [self mouseMoved:event]; } - (void) otherMouseUp:(NSEvent*)event { [self postMouseButtonEvent:event]; } - (void) rightMouseDown:(NSEvent*)event { [self postMouseButtonEvent:event]; } - (void) rightMouseDragged:(NSEvent*)event { [self mouseMoved:event]; } - (void) rightMouseUp:(NSEvent*)event { [self postMouseButtonEvent:event]; } @end /*********************************************************************** * macdrv_create_status_item * * Creates a new status item in the status bar. */ macdrv_status_item macdrv_create_status_item(macdrv_event_queue q) { WineEventQueue* queue = (WineEventQueue*)q; __block WineStatusItem* statusItem; OnMainThread(^{ statusItem = [[WineStatusItem alloc] initWithEventQueue:queue]; }); return (macdrv_status_item)statusItem; } /*********************************************************************** * macdrv_destroy_status_item * * Removes a status item previously returned by * macdrv_create_status_item() from the status bar and destroys it. */ void macdrv_destroy_status_item(macdrv_status_item s) { WineStatusItem* statusItem = (WineStatusItem*)s; OnMainThreadAsync(^{ [statusItem removeFromStatusBar]; [statusItem release]; }); } /*********************************************************************** * macdrv_set_status_item_image * * Sets the image for a status item. If cgimage is NULL, clears the * image of the status item (leaving it a blank spot on the menu bar). */ void macdrv_set_status_item_image(macdrv_status_item s, CGImageRef cgimage) { WineStatusItem* statusItem = (WineStatusItem*)s; CGImageRetain(cgimage); OnMainThreadAsync(^{ NSImage* image = nil; if (cgimage) { NSSize size; CGFloat maxSize = [[NSStatusBar systemStatusBar] thickness]; BOOL changed = FALSE; image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize]; CGImageRelease(cgimage); size = [image size]; while (size.width > maxSize || size.height > maxSize) { size.width /= 2.0; size.height /= 2.0; changed = TRUE; } if (changed) [image setSize:size]; } statusItem.image = image; [image release]; }); } /*********************************************************************** * macdrv_set_status_item_tooltip * * Sets the tooltip string for a status item. If cftip is NULL, clears * the tooltip string for the status item. */ void macdrv_set_status_item_tooltip(macdrv_status_item s, CFStringRef cftip) { WineStatusItem* statusItem = (WineStatusItem*)s; NSString* tip = (NSString*)cftip; if (![tip length]) tip = nil; OnMainThreadAsync(^{ [statusItem setToolTip:tip]; }); }