winemac.drv: Implement systray version 1-4 notifications.
[wine.git] / dlls / winemac.drv / cocoa_status_item.m
blob23d78059585e5694493c5b3ede134378521f53bb
1 /*
2  * MACDRV Cocoa status item class
3  *
4  * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
21 #import <Cocoa/Cocoa.h>
22 #include "macdrv_cocoa.h"
23 #import "cocoa_app.h"
24 #import "cocoa_event.h"
27 @interface WineStatusItem : NSView
29     NSStatusItem* item;
30     WineEventQueue* queue;
31     NSTrackingArea* trackingArea;
32     NSImage* image;
35 @property (retain, nonatomic) NSStatusItem* item;
36 @property (assign, nonatomic) WineEventQueue* queue;
37 @property (retain, nonatomic) NSImage* image;
39 @end
42 @implementation WineStatusItem
44 @synthesize item, queue, image;
46     - (id) initWithEventQueue:(WineEventQueue*)inQueue
47     {
48         NSStatusBar* statusBar = [NSStatusBar systemStatusBar];
49         CGFloat thickness = [statusBar thickness];
51         self = [super initWithFrame:NSMakeRect(0, 0, thickness, thickness)];
52         if (self)
53         {
54             item = [[statusBar statusItemWithLength:NSSquareStatusItemLength] retain];
55             // This is a retain cycle which is broken in -removeFromStatusBar.
56             [item setView:self];
58             queue = inQueue;
60             trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
61                                                         options:NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect
62                                                           owner:self
63                                                        userInfo:nil];
64             [self addTrackingArea:trackingArea];
65         }
66         return self;
67     }
69     - (void) dealloc
70     {
71         if (item)
72         {
73             NSStatusBar* statusBar = [NSStatusBar systemStatusBar];
74             [statusBar removeStatusItem:item];
75             [item release];
76         }
77         [image release];
78         [trackingArea release];
79         [super dealloc];
80     }
82     - (void) setImage:(NSImage*)inImage
83     {
84         if (image != inImage)
85         {
86             [image release];
87             image = [inImage retain];
88             [self setNeedsDisplay:YES];
89         }
90     }
92     - (void) removeFromStatusBar
93     {
94         if (item)
95         {
96             NSStatusBar* statusBar = [NSStatusBar systemStatusBar];
97             [statusBar removeStatusItem:item];
98             [item setView:nil];
100             [queue discardEventsPassingTest:^BOOL (macdrv_event* event){
101                 return ((event->type == STATUS_ITEM_MOUSE_BUTTON && event->status_item_mouse_button.item == (macdrv_status_item)self) ||
102                         (event->type == STATUS_ITEM_MOUSE_MOVE && event->status_item_mouse_move.item == (macdrv_status_item)self));
103             }];
105             self.item = nil;
106         }
107     }
109     - (void) postMouseButtonEvent:(NSEvent*)nsevent;
110     {
111         macdrv_event* event;
112         NSUInteger typeMask = NSEventMaskFromType([nsevent type]);
113         CGPoint point = CGEventGetLocation([nsevent CGEvent]);
115         point = cgpoint_win_from_mac(point);
117         event = macdrv_create_event(STATUS_ITEM_MOUSE_BUTTON, nil);
118         event->status_item_mouse_button.item = (macdrv_status_item)self;
119         event->status_item_mouse_button.button = [nsevent buttonNumber];
120         event->status_item_mouse_button.down = (typeMask & (NSLeftMouseDownMask | NSRightMouseDownMask | NSOtherMouseDownMask)) != 0;
121         event->status_item_mouse_button.count = [nsevent clickCount];
122         event->status_item_mouse_button.x = floor(point.x);
123         event->status_item_mouse_button.y = floor(point.y);
124         [queue postEvent:event];
125         macdrv_release_event(event);
126     }
129     /*
130      * ---------- NSView methods ----------
131      */
132     - (void) drawRect:(NSRect)rect
133     {
134         [item drawStatusBarBackgroundInRect:[self bounds] withHighlight:NO];
136         if (image)
137         {
138             NSSize imageSize = [image size];
139             NSRect bounds = [self bounds];
140             NSPoint imageOrigin = NSMakePoint(NSMidX(bounds) - imageSize.width / 2,
141                                               NSMidY(bounds) - imageSize.height / 2);
143             imageOrigin = [self convertPointToBase:imageOrigin];
144             imageOrigin.x = floor(imageOrigin.x);
145             imageOrigin.y = floor(imageOrigin.y);
146             imageOrigin = [self convertPointFromBase:imageOrigin];
148             [image drawAtPoint:imageOrigin
149                       fromRect:NSZeroRect
150                      operation:NSCompositeSourceOver
151                       fraction:1];
152         }
153     }
156     /*
157      * ---------- NSResponder methods ----------
158      */
159     - (void) mouseDown:(NSEvent*)event
160     {
161         [self postMouseButtonEvent:event];
162     }
164     - (void) mouseDragged:(NSEvent*)event
165     {
166         [self mouseMoved:event];
167     }
169     - (void) mouseMoved:(NSEvent*)nsevent
170     {
171         macdrv_event* event;
172         CGPoint point = CGEventGetLocation([nsevent CGEvent]);
174         point = cgpoint_win_from_mac(point);
176         event = macdrv_create_event(STATUS_ITEM_MOUSE_MOVE, nil);
177         event->status_item_mouse_move.item = (macdrv_status_item)self;
178         event->status_item_mouse_move.x = floor(point.x);
179         event->status_item_mouse_move.y = floor(point.y);
180         [queue postEvent:event];
181         macdrv_release_event(event);
182     }
184     - (void) mouseUp:(NSEvent*)event
185     {
186         [self postMouseButtonEvent:event];
187     }
189     - (void) otherMouseDown:(NSEvent*)event
190     {
191         [self postMouseButtonEvent:event];
192     }
194     - (void) otherMouseDragged:(NSEvent*)event
195     {
196         [self mouseMoved:event];
197     }
199     - (void) otherMouseUp:(NSEvent*)event
200     {
201         [self postMouseButtonEvent:event];
202     }
204     - (void) rightMouseDown:(NSEvent*)event
205     {
206         [self postMouseButtonEvent:event];
207     }
209     - (void) rightMouseDragged:(NSEvent*)event
210     {
211         [self mouseMoved:event];
212     }
214     - (void) rightMouseUp:(NSEvent*)event
215     {
216         [self postMouseButtonEvent:event];
217     }
219 @end
222 /***********************************************************************
223  *              macdrv_create_status_item
225  * Creates a new status item in the status bar.
226  */
227 macdrv_status_item macdrv_create_status_item(macdrv_event_queue q)
229     WineEventQueue* queue = (WineEventQueue*)q;
230     __block WineStatusItem* statusItem;
232     OnMainThread(^{
233         statusItem = [[WineStatusItem alloc] initWithEventQueue:queue];
234     });
236     return (macdrv_status_item)statusItem;
239 /***********************************************************************
240  *              macdrv_destroy_status_item
242  * Removes a status item previously returned by
243  * macdrv_create_status_item() from the status bar and destroys it.
244  */
245 void macdrv_destroy_status_item(macdrv_status_item s)
247     WineStatusItem* statusItem = (WineStatusItem*)s;
249     OnMainThreadAsync(^{
250         [statusItem removeFromStatusBar];
251         [statusItem release];
252     });
255 /***********************************************************************
256  *              macdrv_set_status_item_image
258  * Sets the image for a status item.  If cgimage is NULL, clears the
259  * image of the status item (leaving it a blank spot on the menu bar).
260  */
261 void macdrv_set_status_item_image(macdrv_status_item s, CGImageRef cgimage)
263     WineStatusItem* statusItem = (WineStatusItem*)s;
265     CGImageRetain(cgimage);
267     OnMainThreadAsync(^{
268         NSImage* image = nil;
269         if (cgimage)
270         {
271             NSSize size;
272             CGFloat maxSize = [[NSStatusBar systemStatusBar] thickness];
273             BOOL changed = FALSE;
275             image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
276             CGImageRelease(cgimage);
277             size = [image size];
278             while (size.width > maxSize || size.height > maxSize)
279             {
280                 size.width /= 2.0;
281                 size.height /= 2.0;
282                 changed = TRUE;
283             }
284             if (changed)
285                 [image setSize:size];
286         }
287         statusItem.image = image;
288         [image release];
289     });
292 /***********************************************************************
293  *              macdrv_set_status_item_tooltip
295  * Sets the tooltip string for a status item.  If cftip is NULL, clears
296  * the tooltip string for the status item.
297  */
298 void macdrv_set_status_item_tooltip(macdrv_status_item s, CFStringRef cftip)
300     WineStatusItem* statusItem = (WineStatusItem*)s;
301     NSString* tip = (NSString*)cftip;
303     if (![tip length]) tip = nil;
304     OnMainThreadAsync(^{
305         [statusItem setToolTip:tip];
306     });