custom NSView with video player is not showing (MacOS) - objective-c

I have created a custom NSView with an AVPlayer embedded and it is not showing at all
this is my header:
#interface VideoPlayer : NSView
- (void)loadVideo;
#end
Implementation:
#implementation VideoPlayer
AVPlayer *avPlayer;
AVPlayerLayer* playerLayer;
- (instancetype)initWithFrame:(NSRect)rect {
self = [super initWithFrame:rect];
[self setWantsLayer:YES];
[self setNeedsDisplay:YES];
self.hidden = NO;
return self;
}
-(void) loadVideo {
[avPlayer pause];
avPlayer = nil;
playerLayer = nil;
NSURL *videoURL = [NSURL fileURLWithPath:#"file://~/Documents/tests.mp4"];
//same error with a valid url
//NSURL *videoURL = [NSURL URLWithString:#"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4"];
avPlayer = [AVPlayer playerWithURL:videoURL];
playerLayer = [AVPlayerLayer playerLayerWithPlayer:avPlayer];
playerLayer.frame = self.frame;//or your desired CGRect
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
//playerLayer.masksToBounds = YES;
[self.layer addSublayer:playerLayer];
[avPlayer play];
}
#end
And this is how I am creating the view:
view = [[VideoPlayer alloc] initWithFrame:NSMakeRect(0, 0, 500, 500)];
view.hidden = false;
[view loadVideo];
[parent_view addSubview:view];
I have tried multiple combinations, but the result is always the same... Nothing is being rendered on the application.
On the other hand, if I override the method drawRect in my view like this:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
I am getting a white rectangle where it is suppose to show my video which is expected. So I assume that I am missing something else, but most of the examples are using UIView which is not an option for me at this moment (I am working with some specific constraints)
Any thoughts?
Thank you
Update:
This finally worked for me:
-(void) loadVideo {
AVPlayer *avPlayer = [AVPlayer playerWithURL:[NSURL URLWithString:#"file://localhost/Users/xxxxxxxx/Documents/xxxxxxx.mp4"]];
AVPlayerView *playerView = [[AVPlayerView alloc] initWithFrame:self.bounds];
[playerView setPlayer:avPlayer];
[self addSubview:playerView];
[playerView.player play];
}

To perhaps make things a little clearer the following source code will create a basic AVPlayer for audio files. Note that the addition of both AVKit and AVFoundation frameworks are required for AVPlayerView and AVPlayer respectively. The code uses automatic reference counting and may be run in Terminal or used in Xcode by replacing main.m and AppDelegate and deleting the main nib in info.plist. To run in Terminal, copy/paste code into a file named avplayer.m and use the cmdLine as shown below. Apple docs state that it only runs on MacOS.
/*
To run in Terminal: clang avplayer.m -fobjc-arc -framework Cocoa -framework AVKit -framework AVFoundation -o avplayer && ./avplayer
*/
#import <Cocoa/Cocoa.h>
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
#interface AppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
AVPlayerView *playerView;
}
- (void) openAction;
- (void) buildMenu;
- (void) buildWindow;
#end
#implementation AppDelegate
- (void) openAction {
NSOpenPanel *op = [NSOpenPanel openPanel];
if ( [op runModal] == NSModalResponseOK ) {
AVPlayer *player = [AVPlayer playerWithURL:[op URL]];
[playerView setPlayer:player];
[window setTitleWithRepresentedFilename:[[op URL]lastPathComponent]];
}
}
- (void) buildMenu {
NSMenu *menubar = [NSMenu new];
NSMenuItem *menuBarItem = [NSMenuItem new] ;
[menubar addItem:menuBarItem];
[NSApp setMainMenu:menubar];
NSMenu *appMenu = [NSMenu new];
NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:#"Quit" action:#selector(terminate:) keyEquivalent:#"q"];
[appMenu addItem:quitMenuItem];
[menuBarItem setSubmenu:appMenu];
}
- (void) buildWindow {
#define _wndW 500
#define _wndH 400
window = [[NSWindow alloc] initWithContentRect: NSMakeRect( 0, 0, _wndW, _wndH ) styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskClosable backing: NSBackingStoreBuffered defer: NO];
[window center];
[window setTitle: #"Test window"];
[window makeKeyAndOrderFront: nil];
// **** AVPlayerView **** //
playerView = [[AVPlayerView alloc] initWithFrame:NSMakeRect( 0, 60, _wndW, _wndH - 60)];
[[window contentView] addSubview:playerView];
// **** Open Button **** //
NSButton *myBtn =[[NSButton alloc]initWithFrame:NSMakeRect( 30, 20, 125, 30 )];
[myBtn setBezelStyle:NSBezelStyleRounded ];
[myBtn setTitle: #"Open audio file"];
[myBtn setAction: #selector (openAction)];
[[window contentView] addSubview: myBtn];
// **** Quit btn **** //
NSButton *quitBtn = [[NSButton alloc]initWithFrame:NSMakeRect( _wndW - 50, 5, 40, 40 )];
[quitBtn setBezelStyle:NSBezelStyleCircular ];
[quitBtn setTitle: #"Q" ];
[quitBtn setAutoresizingMask: NSViewMinXMargin];
[quitBtn setAction:#selector(terminate:)];
[[window contentView] addSubview: quitBtn];
}
- (void) applicationWillFinishLaunching: (NSNotification *)notification {
[self buildMenu];
[self buildWindow];
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return YES;
}
#end
int main () {
NSApplication *application = [NSApplication sharedApplication];
AppDelegate *appDelegate = [[AppDelegate alloc] init];
[application setDelegate:appDelegate];
[application run];
return 0;
}

Make sure that you add the AVKit framework to your project.
There is a header in AVKit framework called AVPlayerView.h
Then all you need to do to see the player is to call it:
// **** AVPlayer ***** //
AVPlayerView *playerView = [[AVPlayerView alloc] initWithFrame:NSMakeRect( 0, 0, _wndW, _wndH - 100)];
[[window contentView] addSubview:playerView];
I have a programmatic demo which I could post if you are unable to get it to work.

Related

For Objective-C, the nsmenuitem nsmenu menu bar does not display problems

The menu bar of the MAC should display the menu title and submenus I set.
NSMenu *subMenu = [[NSMenu alloc] initWithTitle:YMLocalizedString(#"assistant.menu.title")];
[subMenu addItems:#[preventRevokeItem,
autoAuthItem,
groupMgrMenu,
newWeChatItem,
forwardAndReplyItem,
]];
NSMenuItem *menuItem = [[NSMenuItem alloc] init];
menuItem.target = self;
menuItem.enabled = NO;
[menuItem setTitle:YMLocalizedString(#"assistant.menu.title")];
[menuItem setSubmenu:subMenu];
[[[NSApplication sharedApplication] mainMenu] addItem:menuItem];
This example may be run in Xcode by deleting the main.m code and replacing it with the following. Also delete the pre-supplied AppDelegate files.
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
}
- (void) menuAction: (id)sender;
- (void) buildMenu;
- (void) buildWindow;
#end
#implementation AppDelegate
- (void) menuAction: (id)sender {
NSBeep();
NSLog(#"%#", sender);
}
- (void) buildMenu {
NSMenu *menubar = [NSMenu new];
NSMenuItem *menuBarItem = [NSMenuItem new];
[menubar addItem:menuBarItem];
[NSApp setMainMenu:menubar];
NSMenu *appMenu = [NSMenu new];
NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:#"Quit"
action:#selector(terminate:) keyEquivalent:#"q"];
[appMenu addItem:quitMenuItem];
[menuBarItem setSubmenu:appMenu];
// **** Asst Menu **** //
NSMenuItem *asstMenuItem = [menubar addItemWithTitle:#"" action:nil keyEquivalent:#""];
NSMenu *asstMenu = [[NSMenu alloc] initWithTitle:#"assistant.menu.title"];
[menubar setSubmenu: asstMenu forItem:asstMenuItem];
NSArray *itemArray = #[#"preventRevokeItem", #"autoAuthItem", #"groupMgrMenu", #"newWeChatItem", #"forwardAndReplyItem"];
[asstMenu addItemWithTitle: itemArray[0] action:#selector(menuAction:) keyEquivalent:#""];
[asstMenu addItemWithTitle: itemArray[1] action:#selector(menuAction:) keyEquivalent:#""];
[asstMenu addItemWithTitle: itemArray[2] action:#selector(menuAction:) keyEquivalent:#""];
[asstMenu addItemWithTitle: itemArray[3] action:#selector(menuAction:) keyEquivalent:#""];
[asstMenu addItemWithTitle: itemArray[4] action:#selector(menuAction:) keyEquivalent:#""];
}
- (void) buildWindow {
#define _wndW 300
#define _wndH 250
window = [[NSWindow alloc] initWithContentRect: NSMakeRect( 0, 0, _wndW, _wndH )
styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable
backing: NSBackingStoreBuffered defer: NO];
[window center];
[window setTitle: #"Test window"];
[window makeKeyAndOrderFront: nil];
// ***** Quit btn ***** //
NSButton *quitBtn = [[NSButton alloc]initWithFrame:NSMakeRect( _wndW - 50, 5, 40, 40 )];
[quitBtn setBezelStyle:NSBezelStyleCircular ];
[quitBtn setTitle: #"Q" ];
[quitBtn setAutoresizingMask: NSViewMinXMargin];
[quitBtn setAction:#selector(terminate:)];
[[window contentView] addSubview: quitBtn];
}
- (void) applicationWillFinishLaunching: (NSNotification *)notification {
[self buildMenu];
[self buildWindow];
}
- (void) applicationDidFinishLaunching: (NSNotification *)notification {
}
#end
int main () {
NSApplication *application = [NSApplication sharedApplication];
AppDelegate *appDelegate = [[AppDelegate alloc] init];
[application setDelegate:appDelegate];
[application run];
return 0;
}

How do I create a modal sheet window exclusively by programming

How do I create a modal sheet window exclusively by programming. Interface builder seems to me over the hand, I'm at the beginning and I want to learn programming, not how I arrange some elements graphically.
I've heard on several programmers that is possible.
I tried without success:
#interface AppDelegate : NSObject <NSApplicationDelegate> {
NSWindow* mywindow;
NSButton* button;
IBOutlet NSPanel* theSheet;
}
- (void) buildWnd;
#end
#implementation AppDelegate
- (void) buildWnd {
button = [[[NSButton alloc] initWithFrame:NSMakeRect(50, 225, 90, 25)] autorelease];
[button setTitle:#"button"];
[button setTarget:self];
[button setAction:#selector(OnbuttonClick:)];
[button setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
mywindow = [[NSWindow alloc] initWithContentRect: NSMakeRect(100, 100, 700, 500) styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable backing: NSBackingStoreBuffered defer: NO];
[mywindow center];
[mywindow setTitle:#"Sheet example"];
[[mywindow contentView] addSubview:button];
[mywindow setIsVisible:YES];
}
- (IBAction) showTheSheet:(id)sender {
}
-(IBAction) endTheSheet:(id)sender {
[mywindow endSheet: theSheet];
}
- (void) applicationWillFinishLaunching: (NSNotification *)notification {
[self buildMenu];
[self buildWnd];
}
- (void) applicationDidFinishLaunching: (NSNotification *)notification {
}
- (IBAction) OnButtonClick:(id)sender {
NSLog(#"OnButtonClick");
[mywindow beginSheet: theSheet
completionHandler:^(NSModalResponse returnCode) {
[NSApp stopModalWithCode: returnCode];
}];
[NSApp runModalForWindow: theSheet];
}
#end
int main() {
NSApplication *application = [NSApplication sharedApplication];
AppDelegate *appDelegate = [[AppDelegate alloc] init];
[application setDelegate:appDelegate];
[application run];
return 0;
}
I receive this error message: AppDelegate OnbuttonClick: unrecognized selector sent to instance.
Where I'm wrong please?
You neglected to set up the sheet. This example should run in Xcode by replacing the main.m with the code below and deleting the pre-supplied AppDelegate files:
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
NSPanel *sheet;
}
- (void) buildWnd;
#end
#implementation AppDelegate
- (void) showBtnAction:(id)sender {
[window beginSheet: sheet completionHandler:^(NSModalResponse returnCode) {
[NSApp stopModalWithCode: returnCode];
}];
[NSApp runModalForWindow: sheet];
}
-(void) hideSheet:(id)sender {
[window endSheet: sheet];
}
- (void) buildWnd {
window = [[NSWindow alloc] initWithContentRect: NSMakeRect(100, 100, 700, 500) styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable backing: NSBackingStoreBuffered defer: NO];
[window center];
[window setTitle:#"Sheet example"];
[window setIsVisible:YES];
// **** Sheet Panel **** //
sheet = [[NSPanel alloc] initWithContentRect:NSMakeRect( 0, 0, 500, 450 )
styleMask:NSWindowStyleMaskDocModalWindow backing:NSBackingStoreBuffered defer:YES];
[sheet center];
[sheet setTitle:#"NSPanel"];
// **** OK Button on Sheet **** //
NSButton *okayBtn = [[NSButton alloc] initWithFrame:NSMakeRect(50, 225, 50, 25)];
[okayBtn setTitle:#"OK"];
[okayBtn setBezelStyle:NSBezelStyleRounded ];
[okayBtn setTarget:self];
[okayBtn setAction:#selector(hideSheet:)];
[[sheet contentView] addSubview:okayBtn];
// **** Show Button **** //
NSButton *showBtn = [[NSButton alloc] initWithFrame:NSMakeRect(50, 225, 150, 25)];
[showBtn setTitle:#"Show sheet"];
[showBtn setBezelStyle:NSBezelStyleRounded ];
[showBtn setAction:#selector(showBtnAction:)];
[showBtn setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
[[window contentView] addSubview:showBtn];
// **** Quit btn **** //
NSButton *quitBtn = [[NSButton alloc]initWithFrame:NSMakeRect(650, 5, 40, 40 )];
[quitBtn setBezelStyle:NSBezelStyleCircular ];
[quitBtn setTitle: #"Q" ];
[quitBtn setAutoresizingMask: NSViewMinXMargin];
[quitBtn setAction:#selector(terminate:)];
[[window contentView] addSubview: quitBtn];
}
- (void) applicationWillFinishLaunching: (NSNotification *)notification {
[self buildWnd];
}
- (void) applicationDidFinishLaunching: (NSNotification *)notification {
}
#end
int main() {
NSApplication *application = [NSApplication sharedApplication];
AppDelegate *appDelegate = [[AppDelegate alloc] init];
[application setDelegate:appDelegate];
[application run];
return 0;
}

Can't change column width of NSTableColumn

I have embedded an NSTableView inside an NSScrollView as shown in this example but for some reason, setting the column width only works correctly when initially setting it. Changing it later, i.e. in response to a button click, doesn't do anything at all. I do it like this:
[col setMinWidth:1000];
[col setMaxWidth:1000];
[col setWidth:1000];
After those calls col.width correctly returns 1000 but the NSTableView doesn't show the change. It still looks as before, i.e. longer entries are still cut off using ... even though the column width is 1000 points now.
I have tried to call [tableView setNeedsDisplay:YES] after changing the column width but it doesn't help. Calling setNeedsDisplay on the NSScrollView doesn't help either. I've also tried playing with NSTableColumn's resizingMask and NSTableView's columnAutoresizingStyle but all to no avail. Trying to change the column width just doesn't work here. Any ideas?
EDIT
Here's the code for reference:
listDelegate = [[MyListDelegate alloc] initWithChoices:array];
scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20, 52, rect.size.width - 2 * 20, 200)];
tableview = [[NSTableView alloc] initWithFrame:NSMakeRect(0, 0, rect.size.width - 2 * 20 - 16, 200)];
column = [[NSTableColumn alloc] initWithIdentifier:#"Column"];
[column setWidth:400];
[tableview addTableColumn:column];
[tableview setHeaderView:nil];
[tableview setDelegate:listDelegate];
[tableview setDataSource:listDelegate];
[tableview reloadData];
[scrollview setDocumentView:tableview];
[scrollview setHasVerticalScroller:YES];
[scrollview setHasHorizontalScroller:YES];
[[win contentView] addSubview:scrollview];
[scrollview release];
[column release];
The list delegate looks like this:
#interface MyListDelegate : NSObject
{
NSArray *choices;
}
- (id)initWithChoices:(NSArray *)c;
#end
#implementation MyListDelegate
- (id)initWithChoices:(NSArray *)c
{
if(!(self = [super init])) return nil;
choices = c;
return self;
}
- (int)numberOfRowsInTableView:(NSTableView *)_tableView
{
return (int) [choices count];
}
- (id)tableView:(NSTableView *)_tableView objectValueForTableColumn:(NSTableColumn *) tableColumn row:(int)row
{
return [choices objectAtIndex:row];
}
- (BOOL)tableView:(NSTableView *)_tableView shouldEditTableColumn:(NSTableColumn *) tableColumn row:(int)row
{
return NO;
}
#end
And when the button is pressed, the following code is executed:
NSTableColumn *col = [[tableview tableColumns] objectAtIndex:0];
[col setMinWidth:1000];
[col setMaxWidth:1000];
[col setWidth:1000];
It looks like a bug to me. [col setMinWidth:1000] also sets the width to 1000 but doesn't update the table view. [col setWidth:1000] doesn't do anything because the width is 1000. Fix: set the width first:
[col setWidth:1000];
[col setMinWidth:1000];
[col setMaxWidth:1000];
Here's one technique to change the column width of a table view.
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate, NSTableViewDelegate, NSTableViewDataSource> {
NSWindow *window;
NSTableColumn *column1;
}
- (void) myBtnAction;
- (void) buildMenu;
- (void) buildWindow;
#end
#implementation AppDelegate
- (void) myBtnAction {
[column1 setWidth:150];
}
- (void) buildMenu {
NSMenu *menubar = [NSMenu new];
NSMenuItem *menuBarItem = [NSMenuItem new];
[menubar addItem:menuBarItem];
[NSApp setMainMenu:menubar];
NSMenu *appMenu = [NSMenu new];
NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:#"Quit" action:#selector(terminate:) keyEquivalent:#"q"];
[appMenu addItem:quitMenuItem];
[menuBarItem setSubmenu:appMenu];
}
- (void) buildWindow {
#define _wndW 500
#define _wndH 350
window = [[NSWindow alloc] initWithContentRect: NSMakeRect( 0, 0, _wndW, _wndH )
styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable
backing: NSBackingStoreBuffered defer: NO];
[window center];
[window setTitle: #"Test window"];
[window makeKeyAndOrderFront: nil];
// **** TableView_SO **** //
NSScrollView *scrlView = [[NSScrollView alloc] initWithFrame:NSMakeRect(60, 100, 380, 200)];
NSTableView *tableView = [[NSTableView alloc] initWithFrame:NSMakeRect(0, 0, 364, 200)];
// create columns for our table
column1 = [[NSTableColumn alloc] initWithIdentifier:#"Col1"];
NSTableColumn * column2 = [[NSTableColumn alloc] initWithIdentifier:#"Col2"];
[column1 setWidth:252];
[column2 setWidth:198];
// generally you want to add at least one column to the table view.
[tableView addTableColumn:column1];
[tableView addTableColumn:column2];
[tableView setDelegate:self];
[tableView setDataSource:self];
[tableView reloadData];
// embed table view in the scroll view, and add the scroll view to window.
[scrlView setDocumentView:tableView];
[scrlView setHasVerticalScroller:YES];
[[window contentView] addSubview:scrlView];
// **** Button **** //
NSButton *myBtn =[[NSButton alloc]initWithFrame:NSMakeRect( 30, 30, 135, 30 )];
[myBtn setBezelStyle:NSBezelStyleRounded ];
[myBtn setTitle: #"Change Col1 Width"];
[myBtn setAction: #selector (myBtnAction)];
[[window contentView] addSubview: myBtn];
// **** Quit btn **** //
NSButton *quitBtn = [[NSButton alloc]initWithFrame:NSMakeRect( _wndW - 50, 5, 40, 40 )];
[quitBtn setBezelStyle:NSBezelStyleCircular ];
[quitBtn setTitle: #"Q" ];
[quitBtn setAutoresizingMask: NSViewMinXMargin];
[quitBtn setAction:#selector(terminate:)];
[[window contentView] addSubview: quitBtn];
}
- (void) applicationWillFinishLaunching: (NSNotification *)notification {
[self buildMenu];
[self buildWindow];
}
- (void) applicationDidFinishLaunching: (NSNotification *)notification {
}
#end
int main (){
NSApplication *application = [NSApplication sharedApplication];
AppDelegate *appDelegate = [[AppDelegate alloc] init];
[application setDelegate:appDelegate];
[application run];
return 0;
}

Sandbox-friendly installation of NSRecentDocumentsMenu without Nib

I am working on a project that does not (currently) use any nib files at all.
I'm trying to figure out whether there is any way to add the NSRecentDocumentsMenu Open Recent menu that doesn't require use of a private API.
The below link is all I've managed to find thus far:
http://lapcatsoftware.com/blog/2007/07/10/working-without-a-nib-part-5-open-recent-menu/
Can anyone offer insights?
I'm trying to figure out whether there is any way to add the NSRecentDocumentsMenu Open Recent menu that doesn't require use of a private API.
The following link is for a document-based app in swift without using nibs:
https://www.dropbox.com/s/cfvsz11oe00lnp5/DocBased_swift.zip?dl=0
It uses a modification of menu code found in this reference:
https://talk.objc.io/episodes/S01E145-setting-up-a-document-based-app
The demo may be run in Xcode by creating a macOS app using swift and a XIB interface, although we will not use the XIB. Create a new file called main.swift and remove the existing AppDelegate class. Copy/paste the source code found above into the main.swift file (use import Cocoa instead of Foundation). When you hit the Run button you will notice that the menus are grayed out and there is no Open Recent menu item. To fix this be certain to add Cocoa NSDocument Class and Role to the info.plist (see image in folder above). The Cocoa NSDocument Class should be the nameOfYourApp.Document for swift or just plain Document for objc. When the Run button is hit after making these additions you should have a document based app with a functioning Open Recent menu item. Please post followup if there are problems.
Objc source code is here:
https://www.dropbox.com/s/dvs1u6lpk4l5xhq/DocBased_objc.zip?dl=0
Instructions are similar to those for swift, except a macOS app is created using objective-c. Copy/paste the source code into main.m after deleting what Xcode supplied. For simplicity, also remove AppDelegate and the Document class (if you checked 'Document based' in the setup), although you could use them if you so desire. The info.plist will also have to be amended with the two additions as noted above.
#import <Cocoa/Cocoa.h>
#interface Document : NSDocument {
NSWindow *window;
NSTextView *txtView;
}
- (void) buildWindow;
#end
#implementation Document
- (id) init {
if (self = [super init]) {
[self buildWindow];
}
return self;
}
+ (BOOL)autosavesInPlace {
return YES;
}
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
return [[txtView string] dataUsingEncoding:NSUTF8StringEncoding];
}
- (BOOL) readFromURL:(NSURL *) absoluteURL ofType:(NSString *)typeName error:(NSError **)outError {
NSString *str = [[NSString alloc] initWithContentsOfURL:absoluteURL encoding:NSUTF8StringEncoding error:outError];
[txtView setString:str];
return YES;
}
- (void) buildWindow {
#define _wndW 780
#define _wndH 700
window = [[NSWindow alloc] initWithContentRect: NSMakeRect( 0, 0, _wndW, _wndH )
styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable
backing: NSBackingStoreBuffered defer: NO];
[window center];
[window setTitle: #"Test window"];
[window cascadeTopLeftFromPoint:NSMakePoint(600,0)];
[window makeKeyAndOrderFront: nil];
// **** Window Controller is Necessary **** //
NSWindowController *windowController = [[NSWindowController alloc]initWithWindow:window];
[self addWindowController: windowController];
// ****** NSTextView with Scroll ****** //
NSScrollView *scrlView = [[NSScrollView alloc] initWithFrame:NSMakeRect( 20, 50, _wndW - 40, _wndH - 70 )];
[[window contentView] addSubview:scrlView];
[scrlView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
[scrlView setHasVerticalScroller: YES];
txtView = [[NSTextView alloc] initWithFrame:NSMakeRect( 0, 0, _wndW - 40, _wndH - 70 )];
[txtView setUsesFindBar:YES];
[txtView setEditable:YES];
[txtView setAllowsUndo: YES];
txtView.automaticQuoteSubstitutionEnabled = NO;
[scrlView setDocumentView: txtView];
// ***** Quit btn ***** //
NSButton *quitBtn = [[NSButton alloc]initWithFrame:NSMakeRect( _wndW - 50, 5, 40, 40 )];
[quitBtn setBezelStyle:NSBezelStyleCircular ];
[quitBtn setTitle: #"Q" ];
[quitBtn setAutoresizingMask: NSViewMinXMargin];
[quitBtn setAction:#selector(terminate:)];
[[window contentView] addSubview: quitBtn];
}
#end
#interface AppDelegate : NSObject <NSApplicationDelegate>
- (void) buildMenu;
#end
#implementation AppDelegate
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender {
return NO;
}
- (void) buildMenu {
NSMenu *mainMenu = [NSMenu new];
[NSApp setMainMenu:mainMenu];
// **** App Menu **** //
NSMenuItem *appMenuItem = [NSMenuItem new];
[mainMenu addItem:appMenuItem];
NSMenu *appMenu = [NSMenu new] ;
[appMenuItem setSubmenu:appMenu];
NSString *appName = [[NSProcessInfo processInfo] processName];
NSString *aboutTitle = [#"About " stringByAppendingString:appName];
[appMenu addItemWithTitle: aboutTitle action:#selector(orderFrontStandardAboutPanel:) keyEquivalent:#""];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle: #"Quit" action:#selector(terminate:) keyEquivalent:#"q"];
// ******** File Menu ********//
NSMenuItem *fileMenuItem = [NSMenuItem new];
[mainMenu addItem:fileMenuItem];
NSMenu *fileMenu = [[NSMenu alloc] initWithTitle:#"File"];
[fileMenuItem setSubmenu:fileMenu];
[fileMenu addItemWithTitle: #"New" action: #selector(newDocument:) keyEquivalent:#"n"];
[fileMenu addItemWithTitle: #"Open..." action: #selector(openDocument:) keyEquivalent:#"o"];
[fileMenu addItem: [NSMenuItem separatorItem]];
[fileMenu addItemWithTitle: #"Close" action: #selector(performClose:) keyEquivalent:#"w"];
[fileMenu addItemWithTitle: #"Save" action: #selector(saveDocument:) keyEquivalent:#"s"];
[fileMenu addItemWithTitle: #"SaveAs..." action: #selector(saveDocumentAs:) keyEquivalent:#""];
// ******** Edit Menu ********//
NSMenuItem *editMenuItem = [NSMenuItem new];
[mainMenu addItem:editMenuItem];
NSMenu *editMenu = [[NSMenu alloc] initWithTitle:#"Edit"] ;
[editMenuItem setSubmenu:editMenu];
[editMenu addItemWithTitle: #"Undo" action:#selector(undo:) keyEquivalent:#"z"];
[editMenu addItemWithTitle: #"Redo" action:#selector(redo:) keyEquivalent:#"Z"];
[editMenu addItem:[NSMenuItem separatorItem]];
[editMenu addItemWithTitle: #"Cut" action:#selector(cut:) keyEquivalent:#"x"];
[editMenu addItemWithTitle: #"Copy" action:#selector(copy:) keyEquivalent:#"c"];
[editMenu addItemWithTitle: #"Paste" action:#selector(paste:) keyEquivalent:#"v"];
[editMenu addItemWithTitle: #"Delete" action:#selector(delete:) keyEquivalent:#""];
[editMenu addItemWithTitle: #"Select All" action:#selector(selectAll:) keyEquivalent:#"a"];
[editMenu addItem:[NSMenuItem separatorItem]];
NSMenuItem *findItem = [editMenu addItemWithTitle:#"Find" action:#selector(performTextFinderAction:) keyEquivalent:#"f"];
[findItem setTag: NSTextFinderActionShowFindInterface];
}
- (void) applicationWillFinishLaunching: (NSNotification *)notification {
[self buildMenu];
}
- (void) applicationDidFinishLaunching: (NSNotification *)notification {
}
#end
int main() {
NSApplication *application = [NSApplication sharedApplication];
AppDelegate *appDelegate = [[AppDelegate alloc] init];
[application setDelegate:appDelegate];
[application run];
return 0;
}
Unable to post image of plist. Sorry.

Change background color on hover on a NSMenuItem with custom NSView

Here is the thing:
I have created a custom NSMenuItem with a custom NSView in it.
Everything works fine except that I can't get the NSMenuItem to get highlighted (= change the background color on mouseover).
I'm trying to do it inside the drawRect method, as shown in other answers posted here.
What am I doing wrong?
The NSView subclass:
#interface customView : NSView
#end
#implementation customView
- (id)initWithFrame:(NSRect)frame
{
NSRect theRect = NSMakeRect(0, 0, 200, 30);
self = [super initWithFrame:theRect];
if (self) {
NSTrackingArea * trackingArea = [[NSTrackingArea alloc] initWithRect:theRect
options: (NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow |NSTrackingActiveAlways)
owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
}
return self;
}
#define menuItem ([self enclosingMenuItem])
- (void) drawRect: (NSRect) rect {
BOOL isHighlighted = [menuItem isHighlighted];
if (isHighlighted) {
//this nslog never happens
NSLog(#"it's highlighted");
}
- (void)mouseUp:(NSEvent*) event {
NSMenuItem* mitem = [self enclosingMenuItem];
NSMenu* m = [mitem menu];
[m cancelTracking];
NSLog(#"you clicked the %ld item",[m indexOfItem: mitem]);
}
#end
The NSMenuItem subclass:
(I add subviews on the custom view here so i can have access to the controls through the NSMenuItem instance)
#interface customItem : NSMenuItem{
}
-(void)setTheText:(NSString*)theString;
#property NSTextField *theLabel;
#end
#import "customItem.h"
#import "customView.h"
#implementation customItem
#synthesize theLabel;
-(id)init{
if (self){
customView *cv = [[customView alloc] init];
theLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 8, 130, 17)];
[theLabel setEditable:NO];
[theLabel setBordered:NO];
NSButton *myButton = [[NSButton alloc] initWithFrame:NSMakeRect(170, 7, 20, 20)];
NSButton *myButton1 = [[NSButton alloc] initWithFrame:NSMakeRect(150, 7, 20, 20)];
[myButton setBezelStyle:NSCircularBezelStyle];
[myButton1 setBezelStyle:NSCircularBezelStyle];
[myButton setTitle:#""];
[myButton1 setTitle:#""];
[cv addSubview:myButton];
[cv addSubview:myButton1];
[cv addSubview:theLabel];
[self setView:cv];
[theLabel setStringValue:#"A Value "];
}
return self;
}
-(void)setTheText:(NSString *)theString{
[theLabel setStringValue:theString];
}
#end
And this is the App Delegate :
#interface AppDelegate : NSObject <NSApplicationDelegate>{
NSStatusItem *statusItem;
IBOutlet NSMenu *theMenu;
}
#property (assign) IBOutlet NSWindow *window;
#end
#import "customItem.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
}
- (void)awakeFromNib{
statusItem = [[NSStatusBar systemStatusBar]
statusItemWithLength:NSSquareStatusItemLength];
NSBundle *bundle = [NSBundle mainBundle];
NSImage *statusImage = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:#"barIcon" ofType:#"png"]];
NSImage *highlightImage = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:#"barIcon_H" ofType:#"png"]];
[statusItem setImage:statusImage];
[statusItem setAlternateImage:highlightImage];
[statusItem setMenu:theMenu];
[theMenu removeAllItems];
customItem *mi = [[customItem alloc] init];
[theMenu addItem:mi];
customItem *mi2 = [[customItem alloc] init];
[theMenu addItem:mi2];
}
#end
This is what i get:
No need to add Booleans or anything else, you can do it from within your custom NSView which is attached to your NSMenuItem
- (void)drawRect:(NSRect)rect {
[super drawRect:rect];
//Handle the hightlight
if ([[self enclosingMenuItem] isHighlighted])
{
[self.lbl_title setTextColor:[NSColor whiteColor]];
[self.lbl_amount setTextColor:[NSColor colorWithDeviceRed:151.0f/255.0f green:164.0f/255.0f blue:179.0f/255.0f alpha:1.0f]];
[[NSColor selectedMenuItemColor] setFill];
}
else
{
[self.lbl_title setTextColor:[NSColor blackColor]];
[self.lbl_amount setTextColor:[NSColor whiteColor]];
[[self backgroundColor] setFill];
}
NSRectFill(rect);}
OK i think i got it.
I added a public bool variable in the NSView subclass.
Then i used
-(void)mouseEntered:(NSEvent *)theEvent
and
-(void)mouseExited:(NSEvent *)theEvent
to set the variable to YES or to NO. After setting the variable i used
[self setNeedsDisplay:YES]
to call
-(void) drawRect: (NSRect) rect
That's how i got it working :)
Here is the right way of changing the background colour (highlighting the item) without maintaining a local variable and force the view to draw again,
-(void)mouseEntered:(NSEvent *)event {
self.layer.backgroundColor = [[NSColor blueColor] colorWithAlphaComponent:0.3].CGColor;
[theLabel setTextColor:[NSColor whiteColor]];
}
-(void)mouseExited:(NSEvent *)event {
self.layer.backgroundColor = [NSColor clearColor].CGColor;
[theLabel setTextColor:[NSColor blackColor]];
}
Make sure you also set [self setWantsLayer:YES] before changing the layer background colour.