NSStatusItem in Xojo

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

Maybe you send me your questions by email and we discuss there?
(support at monkeybreadsoftware.de)

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 :wink:

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 :slight_smile:

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 :slight_smile: 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.

a little example

Download

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.

like this?

http://monkeybreadsoftware.net/examples.shtml