I am trying to use NSStatusItem in xojo for one of my projects. The implementation in MacOSLib works great, but it does not offer all of the features of the real thing in Xcode. In Xcode, mouse events are available so that actions can be performed when the NSStatusItem is clicked on directly instead of when of its drop down menus are clicked on, however this is not available in MacOSLib. Would anyone familiar with declares and implementing objective-c event handling into xojo be able to help me implement this? I think this forum discussion dealt with a similar concept, but I am unsure of how to proceed. Thanks.
We have more in our MBS Plugin.
In general you can put your own view there and handle all the events. e.g. with a CustomNSViewMBS subclass.
Hi Christian, unfortunately I do not have the money to buy the MBS plugins at this time. Would you be able to give me a small guide on how to implement something like the action event in your NSStatusItemMBS? It looks like exactly what I need for my project. Thanks
Ok will do when I’m free later today. Thanks
I think you would need to use setView to set a custom NSView and then add event handlers by overriding the appropriate methods in the NSView. You can use Objective-C runtime calls to subclass the NSView and set methods to handle mouse events. It’s a little tricky, but it can be done. I’m not too sure about how to handle the drawing in the NSView though. Perhaps a Xojo picture object could be used and drawn out to the NSGraphicsContext?
I’ll see if I can get a small example together soon and post it. It might be a while though, lots to do. Something I’d like to have handy too
Hi Jim, sounds great. I look forward to your example.
I was looking through MacOSLib and found a problem (i think) with NSStatusItem.
You should be able to replace all the menu stuff in the NSStatusItem example with
item1.Menu=nil
That would direct the events to the method when the mouse is clicked, but it doesn’t work. I’m sure there is a cleaner way to integrate into NSStatusItem, where the class would default to call the handler without a menu assigned… (Changes have a <-- comment) What I did was to change NSStatusItem.Action.Set to
[code] #if TargetMacOS
declare sub setAction Lib CocoaLib selector “setAction:” (obj_id as Ptr, actionSEL as Ptr)
setAction(self, value)
Target=target_id // <--so that the NSStatusItem can fire the delegate in the target
#else
#pragma unused value
#endif
[/code]
and NSStatusItem.Menu.Set to
[code] #if TargetMacOS
declare sub setMenu lib CocoaLib selector “setMenu:” (obj_id as Ptr, menu as Ptr)
dim menuRef as Ptr
if value <> nil then
menuRef = value
// iteratively traverse the menu tree
dim menus() as NSMenu
menus.append value
for i as integer = 0 to ubound(menus)
dim items() as NSObject = menus(i).items
for each item as NSObject in items
dim mItem as new NSMenuItem(item)
mItem.Target = self.target_id // set target to my target for every item
mItem.Action = "menuAction:" // set action
if mItem.submenu <> nil then
menus.append mItem.submenu
end if
next
next
else
Action=Cocoa.NSSelectorFromString("menuAction:") //<--this adds the default behavior if there is no menu
end if
setMenu self, menuRef
#else
#pragma unused value
#endif
[/code]
Now in the NSStatusItemWindow.open, you can call
item1.menu=nil
and you’ll have the method called on click with no menu. You can also set the click mask as usual. This should allow writing thing like the spotlight menu or Volume control without much hassle
I’ll see if I can contribute the changes to MacOSLib… or maybe there’s a better/more correct way to do it.
Better still, add
menu=nil
to the end of NSStatusItem Constructor. Then the default behavior is to fire without a menu until one is assigned.
The mouseDown code you posted earlier looks like it might come from
https://github.com/djromero/PopoverMenulet/blob/master/PopoverMenulet/WOMMenulet.m
If this is what you’re doing you want to make an NSView subclass, not NSStatusItem. I don’t know macoslib well enough to try subclassing it’s NSView or if there’s a more appropriate way so I created a stand alone class MyView. Just to test, very very crufty. Frame size is hardcoded 100x100, no destructor/release stuff and the dispatch mechanism has cyclic links (look at the NSStatusItem class in macoslib, it does this dispatching too but undoubtedly with elegance).
Then in a pushbutton I created an NSStatusItem, a MyView and linked them together. It just draws a solid red region and msgboxes when clicked.
[code]//import macoslib
//Window1 parts
Property rootItem As NSStatusItem
Property theView As MyView
Sub Action() //Pushbutton
declare sub setView Lib CocoaLib selector “setView:” (obj_id as Ptr, view as Ptr)
rootItem = NSStatusBar.SystemStatusBar.CreateStatusItem(NSStatusBar.NSVariableStatusItemLength, nil)
theView = new MyView
setView(rootItem.id, theView.id)
End Sub
//“NSView subclass”
Class MyView
Property Shared Private dispatch As Dictionary
Property Shared Private MyNSViewSubclass As Ptr
Property Private instanceid As Ptr
Sub Constructor()
soft declare function NSSelectorFromString lib CocoaLib (aSelectorName as CFStringRef) as Ptr
soft declare function objc_allocateClassPair lib CocoaLib (superclass as Ptr, name as CString, extraBytes as Integer) as Ptr
soft declare sub objc_registerClassPair lib CocoaLib (cls as Ptr)
soft declare function NSClassFromString lib CocoaLib (aClassName as CFStringRef) as Ptr
soft declare function class_addMethod lib CocoaLib (cls as Ptr, name as Ptr, imp as Ptr, types as CString) as Boolean
soft declare function alloc lib CocoaLib selector “alloc” (classRef as Ptr) as Ptr
soft declare function initWithFrame lib CocoaLib selector “initWithFrame:” (obj_id as Ptr, frameRect as Cocoa.NSRect) as Ptr
if MyNSViewSubclass = nil then //create subclass
MyNSViewSubclass = objc_allocateClassPair(NSClassFromString("NSView"), "MyNSViewSubclassBleepBlorp", 0)
objc_registerClassPair(MyNSViewSubclass)
if not class_addMethod(MyNSViewSubclass, NSSelectorFromString("mouseDown:"), AddressOf impl_mouseDown, "v@:@") then break
if not class_addMethod(MyNSViewSubclass, NSSelectorFromString("drawRect:"), AddressOf impl_drawRect, "v@:{NSRect=ffff}") then break
end
dim r As Cocoa.NSRect //100x100 nsview frame
r.w = 100
r.h = 100
self.instanceid = initWithFrame(alloc(MyNSViewSubclass), r) //create subclass instance
if dispatch = nil then dispatch = new Dictionary //add to event dispatch (cyclic link)
dispatch.Value(self.instanceid) = self
End Sub
Shared Private Sub impl_drawRect(pid as Ptr, sel as Ptr, dirtyRect As Cocoa.NSRect)
MyView(dispatch.Value(pid)).drawRect(dirtyRect) //pass call to instance
End Sub
Shared Private Sub impl_mouseDown(pid as Ptr, sel as Ptr, evt as Ptr)
MyView(dispatch.Value(pid)).mouseDown(evt)
End Sub
Private Sub drawRect(dirtyRect As Cocoa.NSRect)
declare sub NSRectFill lib “AppKit” (aRect As Cocoa.NSRect)
NSColor.Red.SetColor
NSRectFill(dirtyRect)
End Sub
Private Sub mouseDown(evt As Ptr)
MsgBox “whee”
End Sub
Function id() As Ptr
return instanceid
End Function
End Class[/code]
code copy http://home.comcast.net/~trochoid/code/Window1andMyView.zip.
Instead of manually creating an NSView like this you might be able to pull it directly from a Xojo Canvas or Window. I thought this kind of thing made the View Hierarchy unstable but it looks like the NSPopover example in macoslib does it. Someone else will have to explain how to do that, properly.
Hi Will, thanks for your example. It works very well. I’ve been expanding it and have now hit a wall. I need a way to refresh the drawn area on the menubar with a new different picture or color. Looking through the apple docs, it appears I need to use setNeedsDisplay or setNeedsDisplayInRect. I have tried both with declares, setNeedsDisplay causes a hard crash of the app and setNeedsDisplayInRect appears to do nothing. Do you have any idea how I could properly trigger the Cocoa mouseDown: event?
Thanks
I added a pushbutton to call setNeedsDisplay and altered drawRect to change color each time and it redraws without crashing.
[code]//Window1.Pushbutton2
Sub Action()
declare sub setNeedDisp Lib CocoaLib selector “setNeedsDisplay:” (id as Ptr, flag As boolean)
setNeedDisp(theView.id, true)
End Sub
//MyView
Private Sub drawRect(dirtyRect As Cocoa.NSRect)
declare sub NSRectFill lib “AppKit” (aRect As Cocoa.NSRect)
static pass As integer
pass = pass + 1
if pass mod 2 = 0 then
NSColor.Blue.SetColor
else
NSColor.Red.SetColor
end
NSRectFill(dirtyRect)
End Sub[/code]
What all is it you’re trying to do? I’ve studied Jims code some more and relooked at the docs. I think a click is handled like this: if there’s a custom view then send it mouseDown, else if there’s a menu then display it, else call the action method on the target object, else if there’s no action-target I don’t know If all you need is to act when clicked then Jims approach is much simpler. If you’re showing a popover like in that WOMMenulet example then maybe you do need a custom view to act as the popovers reference. Or maybe the custom view should be in a menuitem?
Another thing I don’t understand is drag highlighting. If I click on my menubars wifi status icon I can drag over bluetooth, volume, battery, time, and appname and the highlighting follows with each dropping something down. Spotlight, notifications and items added with macoslibs NSStatusItem don’t do that, once clicked you can’t drag to the others.
How do you make a NSStatusItem allow dragging to other status items? I tried with a custom view passing the mouseDown events to the views ‘nextResponder’, no dice. I don’t see anything about this in the scant apple docs… https://developer.apple.com/library/mac/documentation/cocoa/Conceptual/StatusBar/StatusBar.html. I’d imagine though you don’t need a custom view to do this.
I think you’ve got it right.
Drag Highlighting…
The reason you can’t click and drag over other items is that the Apple items, not including Spotlight, are all one item! Try this… hold down the Apple key, and click and drag your volume StatusItem… you can rearrange items, but Spotlight will always be on the right, with all other Apple items to the left, and all third party item to left left of that. I think the Apple items are a single view-based item. We will likely never have the ability to drag or rearrange third party items.
Thanks Jim, this makes sense now. Its a very fun and surprisingly easy to use widget.
Hello, guys.
Did you manage to submit the changes to macoslib? I just got a requirement for a menuextra that is able to take clicks to the icon, display a menu AND accept dropped objects. It looks like this is a good place to start but since I’ll be using Macoslib I’d need to add the changes directly myself.
I just found this thread, so I have only downloaded Axel’s example and run it in debug.
[quote=54116:@Christian Schmitz]We have more in our MBS Plugin.
In general you can put your own view there and handle all the events. e.g. with a CustomNSViewMBS subclass.[/quote]
Is there an example doing that ?
TIA
Did you look into examples?
StatusItem with Image in menu.rbp
StatusItem with NSView.rbp
[quote=125596:@Christian Schmitz]Did you look into examples?
StatusItem with Image in menu.rbp
StatusItem with NSView.rbp[/quote]
There are so many examples in so many folders. I had to use spotlight.
A directory of examples would be nice…
Thank you.