is it possible to provide draggable NSTabViewItem,
Basically what i want, if i down L Button on the label of NSTabViewITem and Move, i should allow to drag the TabView item,
I want to do it for Moving of NSTabView Item and have one more feature, if user drag a Label of NSTabView Item and move it to a perticular region, then i should allow to remove that NSTabView Item,
I could able to find only one way of having PSMTab bar, but i have other features also on NSTabView Item that i will be missing if i go with that approach.
Thanks for looking into it,
Somehow i could able to do it.... posting some important piece of code...
1 -- Have to have Custom TabView class for Handling mouse events.
// Interface posted below,
#import <Cocoa/Cocoa.h>
typedef enum __itemDragState{
itemNotDragging = 0,
itemDragStatNormal = 0,
itemDragging = 1,
itemDropped = 2
} ItemDragStat;
#protocol CustomTabViewDelegate <NSObject>
#required
-(bool)allowDrag;
-(bool)allowDrop;
-(void)dragEnter;
-(void)acceptDrop;
-(void)draggingCancelled;
-(void)itemDropped:(id)draggedTabViewItem;
-(void)itemDroppedCompleted:(id)droppedTabViewItem;
#end
#interface CustomTab : NSTabView{
ItemDragStat eItemDragStat;
id draggedItem;
}
#property(assign)id draggedItem;
#end
Now some of the important implementation
#import "CustomTab.h"
#include "Log.h"
#implementation CustomTab
#synthesize draggedItem;
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
# if 0
// don't delete it, might need later on
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
# endif
- (void)mouseUp:(NSEvent *)theEvent{
log(" Mouse up ");
NSPoint location = [self convertPoint: [theEvent locationInWindow]
fromView: nil];
NSTabViewItem *anItem = [self tabViewItemAtPoint: location];
if ( anItem == nil ) {
// if its mouse up else where, reject dragging regardless
eItemDragStat = itemDragStatNormal;
log("Item will not be dropped");
return;
}
if ( ![anItem isEqual:[self selectedTabViewItem]]){
log("Mouse up is in nonselected item");
if ( eItemDragStat == itemDragging){
log("Item will be dropped into this ");
id droppedTabViewItem = anItem;
if ( droppedTabViewItem && [droppedTabViewItem respondsToSelector:#selector(itemDropped:)]){
id selectedTabViewItem = [self selectedTabViewItem];
[droppedTabViewItem performSelector:#selector(itemDropped:) withObject:selectedTabViewItem];
}
}
}
eItemDragStat = itemDragStatNormal;
// return;
// [super mouseUp:theEvent];
}
- (void)mouseDown:(NSEvent *)theEvent{
NSPoint location = [self convertPoint: [theEvent locationInWindow]
fromView: nil];
draggedItem = [self tabViewItemAtPoint:location];
NSTabViewItem *anItem = [self tabViewItemAtPoint: location];
if (anItem != nil && ![anItem isEqual: [self selectedTabViewItem]])
{
[self selectTabViewItem: anItem];
}
}
- (void)mouseDragged:(NSEvent *)theEvent{
NSPoint location = [self convertPoint: [theEvent locationInWindow]
fromView: nil];
id tabViewItemId = [self tabViewItemAtPoint:location];
NSTabViewItem *anItem = [self tabViewItemAtPoint: location];
if (anItem){
if (![anItem isEqual:draggedItem]){
if (tabViewItemId && [tabViewItemId respondsToSelector:#selector(allowDrag)]){
eItemDragStat = itemDragging;
}else{
// drag will be cancelled now.
// tell client item to stop dragging
if (eItemDragStat == itemDragging){
if ( draggedItem && [ draggedItem respondsToSelector:#selector(draggingCancelled)]){
[draggedItem performSelector:#selector(draggingCancelled)];
draggedItem = nil;
}
}
eItemDragStat = itemNotDragging;
// if we have +cursor then it should be reset
}
}else{
log(" Mouse dragged");
}
}else{
// dragging went elsewhere, lets close this dragging operation
if ( draggedItem && [ draggedItem respondsToSelector:#selector(draggingCancelled)]){
[draggedItem performSelector:#selector(draggingCancelled)];
draggedItem = nil;
}
// here reset the mouse pointer
eItemDragStat = itemNotDragging;
}
}
#end
It needs some more fine tuning and its going on....
Related
I set a pan gesture recogniser to recognise my touch on some buttons and am running into the following issue. I am trying to add an action to each of the buttons. I'm testing this by telling each different button to become highlighted when I touch them. So far when I keep my finger pressed on the screen en slide around only button1 and button2 show up (as written in the code).
But for some reason I can still see other buttons highlight the same way when I press them individually. Any idea how to solve this so that they only respond to. If button.tag == 3 etc.. Then respond? Here is the code. (This is all the code in the project and a few buttons in the interface builder.)
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Add Gesture to track the finger
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[self.view addGestureRecognizer:pan];
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateChanged) {
CGPoint point = [gesture locationInView:self.view];
for (UIButton *button in [self.view subviews]) {
if ([button isKindOfClass:[UIButton class]]) {
if (button.tag == 1) {
button.highlighted = CGRectContainsPoint(button.frame, point);
} else if (button.tag == 2) {
button.highlighted = CGRectContainsPoint(button.frame, point);
} //
}
}
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
for (UIButton *button in [self.view subviews]) {
if ([button isKindOfClass:[UIButton class]]) {
button.highlighted = NO;
}
}
}
}
EDIT :
[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanned:)];
- (void)handlePanned:(UIPanGestureRecognizer*)thePanner{
if (thePanner.state == UIGestureRecognizerStateChanged ){
//disable button
}else if (thePanner.state == UIGestureRecognizerStateEnded) {
//enable button
}else if ( thePanner.state == UIGestureRecognizerStateFailed ){
//enable button
}
}
You aren't checking to see if where you are touching is where a button is located.
Quickest solution I see is:
Create properties for each of your buttons, we'll call them button1, button2 and button3.
Create your panGestureRecognizer
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[self.view addGestureRecognizer:pan];
}
Your method to handle the pan:
-(void)handlePanGesture:(UIPanGestureRecognizer *)gesture
{
//create a CGpoint so you know where you are touching
CGPoint touchPoint = [gesture locationInView:self.view];
//just to show you where you are touching...
NSLog(#"%#", NSStringFromCGPoint(touchPoint));
//check your button frame's individually to see if you are touching inside it
if (CGRectContainsPoint(self.button1.frame, touchPoint))
{
NSLog(#"you're panning button1");
}
else if(CGRectContainsPoint(self.button2.frame, touchPoint))
{
NSLog(#"you're panning button2");
}
else if (CGRectContainsPoint(self.button3.frame, touchPoint))
{
NSLog(#"you're panning button3");
}
And that should be it.
From Apple's sample code and reading the docs I can see no way configuring the NSPathControl to behave similarly to e.g. the 'jump bar' in the Xcode Editor window:
I.e. have it represent a path (or other kind of hierarchy) and make each component of the path a clickable popup to navigate the hierarchy..?
Anybody having luck faking such behaviour using a NSPathControlDelegate listening to clicks and showing a menu in a temporary window?
Seems like a common design where one would even expect some OSS implementation - but no such luck yet googling for it..
I made a subclass of NSPathControl so that I can use mouseDown: to popup the context menus of the component cells at the right position. I added also a delegate to the menu to create deeper menus on demand.
- (void)mouseDown:(NSEvent *)event {
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSPathCell *cell = self.cell;
NSPathComponentCell *componentCell = [cell pathComponentCellAtPoint:point
withFrame:self.bounds
inView:self];
NSRect componentRect = [cell rectOfPathComponentCell:componentCell
withFrame:self.bounds
inView:self];
NSMenu *menu = [componentCell menuForEvent:event
inRect:componentRect
ofView:self];
if (menu.numberOfItems > 0) {
NSUInteger selectedMenuItemIndex = 0;
for (NSUInteger menuItemIndex = 0; menuItemIndex < menu.numberOfItems; menuItemIndex++) {
if ([[menu itemAtIndex:menuItemIndex] state] == NSOnState) {
selectedMenuItemIndex = menuItemIndex;
break;
}
}
NSMenuItem *selectedMenuItem = [menu itemAtIndex:selectedMenuItemIndex];
[menu popUpMenuPositioningItem:selectedMenuItem
atLocation:NSMakePoint(NSMinX(componentRect) - 17, NSMinY(componentRect) + 2)
inView:self];
}
}
- (NSMenu *)menuForEvent:(NSEvent *)event {
if (event.type != NSLeftMouseDown) {
return nil;
}
return [super menuForEvent:event];
}
I extended Stephan's answer slightly to accommodate for lazily loading the menu items. I created a small protocol to call for the menu rather than having to build the menu's ahead of time for each cell:
NSPathControlExtended.h
#protocol NSPathControlExtendedDelegate <NSPathControlDelegate>
#required
- (NSMenu *)pathControl:(NSPathControl *)pathControl menuForCell:(NSPathComponentCell *)cell;
#end
#interface NSPathControlExtended : NSPathControl
#property (weak) id <NSPathControlExtendedDelegate> delegate;
#end
NSPathControlExtended.m
#import "NSPathControlExtended.h"
#implementation NSPathControlExtended
#synthesize delegate;
- (instancetype)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
- (void)mouseDown:(NSEvent *)event {
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSPathCell *cell = self.cell;
NSPathComponentCell *componentCell = [cell pathComponentCellAtPoint:point
withFrame:self.bounds
inView:self];
NSRect componentRect = [cell rectOfPathComponentCell:componentCell
withFrame:self.bounds
inView:self];
NSMenu *menu = [delegate pathControl:self menuForCell:componentCell];
if (menu.numberOfItems > 0) {
NSUInteger selectedMenuItemIndex = 0;
for (NSUInteger menuItemIndex = 0; menuItemIndex < menu.numberOfItems; menuItemIndex++) {
if ([[menu itemAtIndex:menuItemIndex] state] == NSOnState) {
selectedMenuItemIndex = menuItemIndex;
break;
}
}
NSMenuItem *selectedMenuItem = [menu itemAtIndex:selectedMenuItemIndex];
[menu popUpMenuPositioningItem:selectedMenuItem
atLocation:NSMakePoint(NSMinX(componentRect) - 17, NSMinY(componentRect) + 2)
inView:self];
}
}
- (NSMenu *)menuForEvent:(NSEvent *)event {
if (event.type != NSLeftMouseDown) {
return nil;
}
return [super menuForEvent:event];
}
#end
My inspiration for this project is the Droplr and CloudApp mac menubar applications. I have been attempting to implement the code explained here.
However, when I implement the code, the menubar images disappear. Here is my code to create the status item:
- (id)init {
self = [super init];
if (self != nil) {
// Install status item into the menu bar
NSStatusItem *statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:STATUS_ITEM_VIEW_WIDTH];
_statusItemView = [[StatusItemView alloc] initWithStatusItem:statusItem];
_statusItemView.image = [NSImage imageNamed:#"Status"];
_statusItemView.alternateImage = [NSImage imageNamed:#"StatusHighlighted"];
_statusItemView.action = #selector(togglePanel:);
StatusItemView* dragView = [[StatusItemView alloc] initWithFrame:NSMakeRect(0, 0, 24, 24)];
[statusItem setView:dragView];
[dragView release];
}
return self;
}
This is my view file:
#import "StatusItemView.h"
#implementation StatusItemView
#synthesize statusItem = _statusItem;
#synthesize image = _image;
#synthesize alternateImage = _alternateImage;
#synthesize isHighlighted = _isHighlighted;
#synthesize action = _action;
#synthesize target = _target;
#pragma mark -
- (id)initWithStatusItem:(NSStatusItem *)statusItem {
CGFloat itemWidth = [statusItem length];
CGFloat itemHeight = [[NSStatusBar systemStatusBar] thickness];
NSRect itemRect = NSMakeRect(0.0, 0.0, itemWidth, itemHeight);
self = [super initWithFrame:itemRect];
if (self != nil) {
_statusItem = statusItem;
_statusItem.view = self;
}
return self;
}
#pragma mark -
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
//register for drags
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
}
return self;
}
//we want to copy the files
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
return NSDragOperationCopy;
}
perform the drag and log the files that are dropped
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];
if( [[pboard types] containsObject:NSFilenamesPboardType] ) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
NSLog(#"Files: %#",files);
}
return YES;
}
#pragma mark -
- (void)drawRect:(NSRect)dirtyRect {
[self.statusItem drawStatusBarBackgroundInRect:dirtyRect withHighlight:self.isHighlighted];
NSImage *icon = self.isHighlighted ? self.alternateImage : self.image;
NSSize iconSize = [icon size];
NSRect bounds = self.bounds;
CGFloat iconX = roundf((NSWidth(bounds) - iconSize.width) / 2);
CGFloat iconY = roundf((NSHeight(bounds) - iconSize.height) / 2);
NSPoint iconPoint = NSMakePoint(iconX, iconY);
[icon compositeToPoint:iconPoint operation:NSCompositeSourceOver];
}
#pragma mark -
#pragma mark Mouse tracking
- (void)mouseDown:(NSEvent *)theEvent {
[NSApp sendAction:self.action to:self.target from:self];
}
#pragma mark -
#pragma mark Accessors
- (void)setHighlighted:(BOOL)newFlag {
if (_isHighlighted == newFlag) return;
_isHighlighted = newFlag;
[self setNeedsDisplay:YES];
}
#pragma mark -
- (void)setImage:(NSImage *)newImage {
if (_image != newImage) {
_image = newImage;
[self setNeedsDisplay:YES];
}
}
- (void)setAlternateImage:(NSImage *)newImage {
if (_alternateImage != newImage) {
_alternateImage = newImage;
if (self.isHighlighted) {
[self setNeedsDisplay:YES];
}
}
}
#pragma mark -
- (NSRect)globalRect {
NSRect frame = [self frame];
frame.origin = [self.window convertBaseToScreen:frame.origin];
return frame;
}
#end
Thanks everyone!
I know this is an old question but perhaps this might help:
Try setting the NSStatusItem *statusItem as a #propery of the class. If you have ARC, the menubar might be getting destroyed right after the init function finishes.
I'm delving into the new world of UIPageViewControllers and there are a lot of tutorials out there, however all of them seem to create one view, and then just use new instances of it with different content.
I'd really like to be able to create multiple XIBs and then just chain them together with the UIPageViewController but it's too new and I can't get my head around the way it works.
Well, here's a long answer that you should be able to copy and paste. (This code was adapted from Erica Sadun (https://github.com/erica/iOS-5-Cookbook))
First, create a new class of type UIPageViewController. Call it BookController. Now paste the following code in your .h file.
// Used for storing the most recent book page used
#define DEFAULTS_BOOKPAGE #"BookControllerMostRecentPage"
#protocol BookControllerDelegate <NSObject>
- (id) viewControllerForPage: (int) pageNumber;
#optional
- (void) bookControllerDidTurnToPage: (NSNumber *) pageNumber;
#end
#interface BookController : UIPageViewController <UIPageViewControllerDelegate, UIPageViewControllerDataSource>
+ (id) bookWithDelegate: (id) theDelegate;
+ (id) rotatableViewController;
- (void) moveToPage: (uint) requestedPage;
- (int) currentPage;
#property (assign) id <BookControllerDelegate> bookDelegate;
#property (nonatomic, assign) uint pageNumber;
and in your .m file:
#define IS_IPHONE ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
#define SAFE_ADD(_Array_, _Object_) {if (_Object_ && [_Array_ isKindOfClass:[NSMutableArray class]]) [pageControllers addObject:_Object_];}
#define SAFE_PERFORM_WITH_ARG(THE_OBJECT, THE_SELECTOR, THE_ARG) (([THE_OBJECT respondsToSelector:THE_SELECTOR]) ? [THE_OBJECT performSelector:THE_SELECTOR withObject:THE_ARG] : nil)
#pragma Utility Class - VC that Rotates
#interface RotatableVC : UIViewController
#end
#implementation RotatableVC
- (void) loadView
{
[super loadView];
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.view.backgroundColor = [UIColor whiteColor];
}
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
#end
#pragma Book Controller
#implementation BookController
#synthesize bookDelegate, pageNumber;
#pragma mark Debug / Utility
- (int) currentPage
{
int pageCheck = ((UIViewController *)[self.viewControllers objectAtIndex:0]).view.tag;
return pageCheck;
}
#pragma mark Page Handling
// Update if you'd rather use some other decision style
- (BOOL) useSideBySide: (UIInterfaceOrientation) orientation
{
BOOL isLandscape = UIInterfaceOrientationIsLandscape(orientation);
return isLandscape;
}
// Store the new page and update the delegate
- (void) updatePageTo: (uint) newPageNumber
{
pageNumber = newPageNumber;
[[NSUserDefaults standardUserDefaults] setInteger:pageNumber forKey:DEFAULTS_BOOKPAGE];
[[NSUserDefaults standardUserDefaults] synchronize];
SAFE_PERFORM_WITH_ARG(bookDelegate, #selector(bookControllerDidTurnToPage:), [NSNumber numberWithInt:pageNumber]);
}
// Request controller from delegate
- (UIViewController *) controllerAtPage: (int) aPageNumber
{
if (bookDelegate &&
[bookDelegate respondsToSelector:#selector(viewControllerForPage:)])
{
UIViewController *controller = [bookDelegate viewControllerForPage:aPageNumber];
controller.view.tag = aPageNumber;
return controller;
}
return nil;
}
// Update interface to the given page
- (void) fetchControllersForPage: (uint) requestedPage orientation: (UIInterfaceOrientation) orientation
{
BOOL sideBySide = [self useSideBySide:orientation];
int numberOfPagesNeeded = sideBySide ? 2 : 1;
int currentCount = self.viewControllers.count;
uint leftPage = requestedPage;
if (sideBySide && (leftPage % 2)) leftPage--;
// Only check against current page when count is appropriate
if (currentCount && (currentCount == numberOfPagesNeeded))
{
if (pageNumber == requestedPage) return;
if (pageNumber == leftPage) return;
}
// Decide the prevailing direction by checking the new page against the old
UIPageViewControllerNavigationDirection direction = (requestedPage > pageNumber) ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse;
[self updatePageTo:requestedPage];
// Update the controllers
NSMutableArray *pageControllers = [NSMutableArray array];
SAFE_ADD(pageControllers, [self controllerAtPage:leftPage]);
if (sideBySide)
SAFE_ADD(pageControllers, [self controllerAtPage:leftPage + 1]);
[self setViewControllers:pageControllers direction: direction animated:YES completion:nil];
}
// Entry point for external move request
- (void) moveToPage: (uint) requestedPage
{
[self fetchControllersForPage:requestedPage orientation:(UIInterfaceOrientation)[UIDevice currentDevice].orientation];
}
#pragma mark Data Source
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
[self updatePageTo:pageNumber + 1];
return [self controllerAtPage:(viewController.view.tag + 1)];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
[self updatePageTo:pageNumber - 1];
return [self controllerAtPage:(viewController.view.tag - 1)];
}
#pragma mark Delegate
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
NSUInteger indexOfCurrentViewController = 0;
if (self.viewControllers.count)
indexOfCurrentViewController = ((UIViewController *)[self.viewControllers objectAtIndex:0]).view.tag;
[self fetchControllersForPage:indexOfCurrentViewController orientation:orientation];
BOOL sideBySide = [self useSideBySide:orientation];
self.doubleSided = sideBySide;
UIPageViewControllerSpineLocation spineLocation = sideBySide ? UIPageViewControllerSpineLocationMid : UIPageViewControllerSpineLocationMin;
return spineLocation;
}
-(void)dealloc{
self.bookDelegate = nil;
}
#pragma mark Class utility routines
// Return a UIViewController that knows how to rotate
+ (id) rotatableViewController
{
UIViewController *vc = [[RotatableVC alloc] init];
return vc;
}
// Return a new book
+ (id) bookWithDelegate: (id) theDelegate
{
BookController *bc = [[BookController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
bc.dataSource = bc;
bc.delegate = bc;
bc.bookDelegate = theDelegate;
return bc;
}
This Class can now be used to control any book you create in any project, and for multiple books in a single project. For each book, create a delegate UIPageViewController with the #interface:
#interface NameOfBookController : UIPageViewController <BookControllerDelegate>
In the .m file of this delegate, include:
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
[super loadView];
CGRect appRect = [[UIScreen mainScreen] applicationFrame];
self.view = [[UIView alloc] initWithFrame: appRect];
self.view.backgroundColor = [UIColor whiteColor];
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
// Establish the page view controller
bookController = [BookController bookWithDelegate:self];
bookController.view.frame = (CGRect){.size = appRect.size};
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
// Add the child controller, and set it to the first page
[self.view addSubview:bookController.view];
[self addChildViewController:bookController];
[bookController didMoveToParentViewController:self];
}
Then add:
- (id) viewControllerForPage: (int) pageNumber
{
// Establish a new controller
UIViewController *controller;
switch (pageNumber) {
case 0:
view1 = [[FirstViewController alloc] init];
view1.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
controller = view1;
//rinse and repeat with each new view controller
break;
case 1:
//etc.
break;
default:
return nil;
break;
}
return controller;
}
For the record, this code is not memory-safe. If using ARC, add in your #autoreleasePool{}; if not, don't forget your retain/release cycle.
I hope this helps!
This article shows how to create an app using UIPageViewController with custom viewcontrollers for each page: http://www.informit.com/articles/article.aspx?p=1760500&seqNum=6
I'm working on a status bar app that has a left and right click. I've got the start of this working by following the tips from other posts but I'm not sure how to go about showing a menu on right click.
I use a subclassed NSView as the custom view of my NSStatusItem and have the right and left clicks executing different functions:
- (void)mouseDown:(NSEvent *)theEvent{
[super mouseDown:theEvent];
if ([theEvent modifierFlags] & NSCommandKeyMask){
[self.target performSelectorOnMainThread:self.rightAction withObject:nil waitUntilDone:NO];
}else{
[self.target performSelectorOnMainThread:self.action withObject:nil waitUntilDone:NO];
}
}
- (void)rightMouseDown:(NSEvent *)theEvent{
[super rightMouseDown:theEvent];
[self.target performSelectorOnMainThread:self.rightAction withObject:nil waitUntilDone:NO];
}
How can I show a menu on right click, the same way the standard NSStatusItem does on left click?
NSStatusItem popUpStatusItemMenu: did the trick. I am calling it from my right click action and passing in the menu I want to show and it's showing it! This is not what I would have expected this function to do, but it's working.
Here's the important parts of what my code looks like:
- (void)showMenu{
// check if we are showing the highlighted state of the custom status item view
if(self.statusItemView.clicked){
// show the right click menu
[self.statusItem popUpStatusItemMenu:self.rightClickMenu];
}
}
// menu delegate method to unhighlight the custom status bar item view
- (void)menuDidClose:(NSMenu *)menu{
[self.statusItemView setHighlightState:NO];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
// setup custom view that implements mouseDown: and rightMouseDown:
self.statusItemView = [[ISStatusItemView alloc] init];
self.statusItemView.image = [NSImage imageNamed:#"menu.png"];
self.statusItemView.alternateImage = [NSImage imageNamed:#"menu_alt.png"];
self.statusItemView.target = self;
self.statusItemView.action = #selector(mainAction);
self.statusItemView.rightAction = #selector(showMenu);
// set menu delegate
[self.rightClickMenu setDelegate:self];
// use the custom view in the status bar item
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
[self.statusItem setView:self.statusItemView];
}
Here is the implementation for the custom view:
#implementation ISStatusItemView
#synthesize image = _image;
#synthesize alternateImage = _alternateImage;
#synthesize clicked = _clicked;
#synthesize action = _action;
#synthesize rightAction = _rightAction;
#synthesize target = _target;
- (void)setHighlightState:(BOOL)state{
if(self.clicked != state){
self.clicked = state;
[self setNeedsDisplay:YES];
}
}
- (void)drawImage:(NSImage *)aImage centeredInRect:(NSRect)aRect{
NSRect imageRect = NSMakeRect((CGFloat)round(aRect.size.width*0.5f-aImage.size.width*0.5f),
(CGFloat)round(aRect.size.height*0.5f-aImage.size.height*0.5f),
aImage.size.width,
aImage.size.height);
[aImage drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0f];
}
- (void)drawRect:(NSRect)rect{
if(self.clicked){
[[NSColor selectedMenuItemColor] set];
NSRectFill(rect);
if(self.alternateImage){
[self drawImage:self.alternateImage centeredInRect:rect];
}else if(self.image){
[self drawImage:self.image centeredInRect:rect];
}
}else if(self.image){
[self drawImage:self.image centeredInRect:rect];
}
}
- (void)mouseDown:(NSEvent *)theEvent{
[super mouseDown:theEvent];
[self setHighlightState:!self.clicked];
if ([theEvent modifierFlags] & NSCommandKeyMask){
[self.target performSelectorOnMainThread:self.rightAction withObject:nil waitUntilDone:NO];
}else{
[self.target performSelectorOnMainThread:self.action withObject:nil waitUntilDone:NO];
}
}
- (void)rightMouseDown:(NSEvent *)theEvent{
[super rightMouseDown:theEvent];
[self setHighlightState:!self.clicked];
[self.target performSelectorOnMainThread:self.rightAction withObject:nil waitUntilDone:NO];
}
- (void)dealloc{
self.target = nil;
self.action = nil;
self.rightAction = nil;
[super dealloc];
}
#end
One option is to just fake the left mouse down:
- (void)rightMouseDown: (NSEvent *)event {
NSEvent * newEvent;
newEvent = [NSEvent mouseEventWithType:NSLeftMouseDown
location:[event locationInWindow]
modifierFlags:[event modifierFlags]
timestamp:CFAbsoluteTimeGetCurrent()
windowNumber:[event windowNumber]
context:[event context]
eventNumber:[event eventNumber]
clickCount:[event clickCount]
pressure:[event pressure]];
[self mouseDown:newEvent];
}
Added little something for when you need title in your view
- (void)drawRect:(NSRect)rect{
if(self.clicked){
[[NSColor selectedMenuItemColor] set];
NSRectFill(rect);
if(self.alternateImage){
[self drawImage:self.alternateImage centeredInRect:rect];
}else if(self.image){
[self drawImage:self.image centeredInRect:rect];
} else {
[self drawTitleInRect:rect];
}
} else if(self.image){
[self drawImage:self.image centeredInRect:rect];
} else {
[self drawTitleInRect:rect];
}
}
-(void)drawTitleInRect:(CGRect)rect
{
CGSize size = [_title sizeWithAttributes:nil];
CGRect newRect = CGRectMake(MAX((rect.size.width - size.width)/2.f,0.f),
MAX((rect.size.height - size.height)/2.f,0.f),
size.width,
size.height);
NSDictionary *attributes = #{NSForegroundColorAttributeName : self.clicked?[NSColor highlightColor]:[NSColor textColor]
};
[_title drawInRect:newRect withAttributes:attributes];
}
- (void)statusItemAction {
NSEvent *event = NSApp.currentEvent;
if (event.type == NSEventTypeRightMouseDown || (event.modifierFlags & NSEventModifierFlagControl)) {
[self toggleMenu];
} else {
[self togglePopOver];
}
}