Page 1 of 1

Non-Standard item in a ToolBar

Posted: Sat Mar 22, 2014 1:27 pm
by ergrull0
Hi all guys! It's me again! With another humble question... :oops:

Is it possible to add "non-standard" items in a toolbar using some sort of CocoaMessage() code? And how?

This is what I mean:

Image

Thanks!!! 8)

Re: Non-Standard item in a ToolBar

Posted: Sun Mar 23, 2014 11:42 am
by Shardik
This is a first try using a wierd hack by replacing a toolbar button with a search field. But because a search field is no native PureBasic control, I currently don't see a way how to detect and react on entries into the search field... :oops:

Therefore it seems to be necessary to implement a toolbar the hard way using API functions and delegate methods in order to display non-PB controls.

Code: Select all

EnableExplicit

#NSToolbarSizeModeRegular = 1

Define ItemArray.I
Define ItemSize.NSSize
Define SearchField.I
Define SearchFieldFrame.NSRect
Define ToolbarItem.I

OpenWindow(0, 270, 100, 260, 50, "ToolBar with search field",
  #PB_Window_SystemMenu | #PB_Window_Invisible)

; ----- Define search field
SearchField = CocoaMessage(0, 0, "NSSearchField new")
CocoaMessage(0, SearchField, "sizeToFit")
CocoaMessage(@SearchFieldFrame, SearchField, "frame")
ToolbarItem = CocoaMessage(0, CocoaMessage(0, 0, "NSToolbarItem alloc"),
  "initWithItemIdentifier:$", @"SearchToolbarItemIdentifier")

; ----- Create empty dummy image
CreateImage(0, 24, 24)

; ----- Create standard ToolBar
CreateToolBar(0, WindowID(0))

; ----- Change size of ToolBar to larger icons
CocoaMessage(0, ToolBarID(0), "setSizeMode:", #NSToolbarSizeModeRegular)

; ----- Add empty dummy image to ToolBar
ToolBarImageButton(0, ImageID(0))

; ----- Get item object of ToolBar button
ItemArray = CocoaMessage(0, ToolBarID(0), "items")
ToolbarItem = CocoaMessage(0, ItemArray, "objectAtIndex:", 0)

; ----- Replace ToolBar button by search field
CocoaMessage(0, ToolbarItem, "setView:", SearchField)

; ----- Change ToolBar button size to search field size
ItemSize\width = 150
ItemSize\height = 23
CocoaMessage(0, ToolbarItem, "setMaxSize:@", @ItemSize)
CocoaMessage(0, ToolbarItem, "setMinSize:@", @ItemSize)

; ----- Make window visible now in order to hide previous ToolBar modification
HideWindow(0, #False)

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow

Re: Non-Standard item in a ToolBar

Posted: Sun Mar 23, 2014 12:40 pm
by wilbert
Shardik wrote:This is a first try using a wierd hack by replacing a toolbar button with a search field. But because a search field is no native PureBasic control, I currently don't see a way how to detect and react on entries into the search field... :oops:
NSSearchField is a subclass of NSControl.
Using setTarget: and setAction: you can react on it.
It's a bit like a delegate method but in this case you can define your own selector for the action.

Re: Non-Standard item in a ToolBar

Posted: Sun Mar 23, 2014 4:17 pm
by Shardik
Thank you Wilbert for your hint. I remember having seen your use of setTag:, setTarget: and SetAction: in your example of a Target/Action-Callback for live tracking in a TrackBarGadget and I have utilized it already in discriminating live tracking in multiple TrackBarGadgets... :oops:

I have now enhanced my above example with detecting each letter entry in the search field inside the ToolBar:

Code: Select all

EnableExplicit

#NSToolbarSizeModeRegular = 1

ImportC ""
  sel_registerName(MethodName.S)
  class_addMethod(Class.I, Selector.I, Implementation.I, Types.S)
EndImport

Define ItemArray.I
Define ItemSize.NSSize
Define SearchField.I
Define SearchFieldFrame.NSRect
Define ToolbarItem.I

ProcedureC ToolBarCallback(Object.I, Selector.I, Sender.I)
  Shared SearchField.I

  If Sender = SearchField
    Debug "New content of search field: " +
      PeekS(CocoaMessage(0, CocoaMessage(0, Sender, "stringValue"),
      "UTF8String"), -1, #PB_UTF8)
  EndIf
EndProcedure

Procedure SetToolBarCallback(Callback.I, Control.I)
  Protected AppDelegate.I
  Protected DelegateClass.I

  AppDelegate = CocoaMessage(0, CocoaMessage(0, 0,
    "NSApplication sharedApplication"), "delegate")
  DelegateClass.I = CocoaMessage(0, AppDelegate, "class")
  class_addMethod(DelegateClass, sel_registerName("forwardAction:"), Callback,
    "v@:@")
  CocoaMessage(0, Control, "setAction:", sel_registerName("forwardAction:"))
EndProcedure

OpenWindow(0, 270, 100, 260, 50, "ToolBar with search field",
  #PB_Window_SystemMenu | #PB_Window_Invisible)

; ----- Define search field
SearchField = CocoaMessage(0, 0, "NSSearchField new")
CocoaMessage(0, SearchField, "sizeToFit")
CocoaMessage(@SearchFieldFrame, SearchField, "frame")
ToolbarItem = CocoaMessage(0, CocoaMessage(0, 0, "NSToolbarItem alloc"),
  "initWithItemIdentifier:$", @"SearchToolbarItemIdentifier")
SetToolBarCallback(@ToolBarCallback(), SearchField)

; ----- Create empty dummy image
CreateImage(0, 24, 24)

; ----- Create standard ToolBar
CreateToolBar(0, WindowID(0))

; ----- Change size of ToolBar to larger icons
CocoaMessage(0, ToolBarID(0), "setSizeMode:", #NSToolbarSizeModeRegular)

; ----- Add button with empty dummy image to ToolBar
ToolBarImageButton(0, ImageID(0))

; ----- Get item object of ToolBar button
ItemArray = CocoaMessage(0, ToolBarID(0), "items")
ToolbarItem = CocoaMessage(0, ItemArray, "objectAtIndex:", 0)

; ----- Replace ToolBar button with search field
CocoaMessage(0, ToolbarItem, "setView:", SearchField)

; ----- Change ToolBar button size to search field size
ItemSize\width = 150
ItemSize\height = 23
CocoaMessage(0, ToolbarItem, "setMaxSize:@", @ItemSize)
CocoaMessage(0, ToolbarItem, "setMinSize:@", @ItemSize)

; ----- Make window visible now in order to hide previous ToolBar modification
HideWindow(0, #False)

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
Update: I removed setTag: and setTarget: from SetToolBarCallback() because Wilbert stated that they are not necessary (thank you again for your heads up, Wilbert!) and I defined SearchField as Shared in ToolBarCallback() to be able to discriminate between different ToolBar controls.

Re: Non-Standard item in a ToolBar

Posted: Sun Mar 23, 2014 4:46 pm
by wilbert
@Shardik,
I used setTag: to create some sort of universal option with a single callback but you don't need that in this case.
setTarget: is required when you use PureBasic gadgets to overwrite the target PB sets but if you create an object like a search field yourself, the target is nil by default meaning that it will follow the responder chain that ends with the app delegate.
So in this case the only thing you need to set on the control is the action with setAction: the other two can be omitted.
If you would add multiple custom objects like for example also a NSSegmentedControl and make the objects global variables, you could simply identify by the sender what object is calling the toolbar callback.
One concern might be the .S in your import statements when using unicode.

Here's a modified version of your example with a more general approach for the callback

Code: Select all

EnableExplicit

ImportC ""
  class_addMethod(cls.i, name.i, imp.i, *types)
  sel_registerName(*str)
EndImport

DataSection
  types_myActionCB:
  !db 'v@:@', 0
  sel_myActionCB:
  !db 'myActionCB:',0
EndDataSection

Global.i SEL_MyActionCB = sel_registerName(?sel_myActionCB)

DeclareC MyActionCB(Object.i, Selector.i, Sender.i)

Procedure InitMyActionCB()
  Protected AppDelegate.i = CocoaMessage(0, CocoaMessage(0, 0, "NSApplication sharedApplication"), "delegate")
  Protected DelegateClass.i = CocoaMessage(0, AppDelegate, "class")
  class_addMethod(DelegateClass, SEL_MyActionCB, @MyActionCB(), ?types_myActionCB)
EndProcedure

InitMyActionCB()



; *** main code ***

#NSToolbarSizeModeRegular = 1

Global.i SearchField

ProcedureC MyActionCB(Object.i, Selector.i, Sender.i)
  Select Sender
    Case SearchField
      Debug "New content of search field: " +
            PeekS(CocoaMessage(0, CocoaMessage(0, Sender, "stringValue"),  
                               "UTF8String"), -1, #PB_UTF8)
  EndSelect
EndProcedure

Define ItemArray.i
Define ItemSize.NSSize
Define SearchFieldFrame.NSRect
Define ToolbarItem.i

OpenWindow(0, 270, 100, 260, 50, "ToolBar with search field",
           #PB_Window_SystemMenu | #PB_Window_Invisible)

; ----- Define search field
SearchField = CocoaMessage(0, 0, "NSSearchField new")
CocoaMessage(0, SearchField, "sizeToFit")
CocoaMessage(@SearchFieldFrame, SearchField, "frame")
ToolbarItem = CocoaMessage(0, CocoaMessage(0, 0, "NSToolbarItem alloc"),
                           "initWithItemIdentifier:$", @"SearchToolbarItemIdentifier")
CocoaMessage(0, SearchField, "setAction:", SEL_MyActionCB)

; ----- Create empty dummy image
CreateImage(0, 24, 24)

; ----- Create standard ToolBar
CreateToolBar(0, WindowID(0))

; ----- Change size of ToolBar to larger icons
CocoaMessage(0, ToolBarID(0), "setSizeMode:", #NSToolbarSizeModeRegular)

; ----- Add button with empty dummy image to ToolBar
ToolBarImageButton(0, ImageID(0))

; ----- Get item object of ToolBar button
ItemArray = CocoaMessage(0, ToolBarID(0), "items")
ToolbarItem = CocoaMessage(0, ItemArray, "objectAtIndex:", 0)

; ----- Replace ToolBar button with search field
CocoaMessage(0, ToolbarItem, "setView:", SearchField)

; ----- Change ToolBar button size to search field size
ItemSize\width = 150
ItemSize\height = 23
CocoaMessage(0, ToolbarItem, "setMaxSize:@", @ItemSize)
CocoaMessage(0, ToolbarItem, "setMinSize:@", @ItemSize)

; ----- Make window visible now in order to hide previous ToolBar modification
HideWindow(0, #False)

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
PB 5.40+

Code: Select all

EnableExplicit

Global.i SEL_MyActionCB = sel_registerName_("myActionCB:")

DeclareC MyActionCB(Object.i, Selector.i, Sender.i)

Procedure InitMyActionCB()
  Protected AppDelegate.i = CocoaMessage(0, CocoaMessage(0, 0, "NSApplication sharedApplication"), "delegate")
  Protected DelegateClass.i = CocoaMessage(0, AppDelegate, "class")
  class_addMethod_(DelegateClass, SEL_MyActionCB, @MyActionCB(), "v@:@")
EndProcedure

InitMyActionCB()



; *** main code ***

#NSToolbarSizeModeRegular = 1

Global.i SearchField

ProcedureC MyActionCB(Object.i, Selector.i, Sender.i)
  Select Sender
    Case SearchField
      Debug "New content of search field: " +
            PeekS(CocoaMessage(0, CocoaMessage(0, Sender, "stringValue"),  
                               "UTF8String"), -1, #PB_UTF8)
  EndSelect
EndProcedure

Define ItemArray.i
Define ItemSize.NSSize
Define SearchFieldFrame.NSRect
Define ToolbarItem.i

OpenWindow(0, 270, 100, 260, 50, "ToolBar with search field",
           #PB_Window_SystemMenu | #PB_Window_Invisible)

; ----- Define search field
SearchField = CocoaMessage(0, 0, "NSSearchField new")
CocoaMessage(0, SearchField, "sizeToFit")
CocoaMessage(@SearchFieldFrame, SearchField, "frame")
ToolbarItem = CocoaMessage(0, CocoaMessage(0, 0, "NSToolbarItem alloc"),
                           "initWithItemIdentifier:$", @"SearchToolbarItemIdentifier")
CocoaMessage(0, SearchField, "setAction:", SEL_MyActionCB)

; ----- Create empty dummy image
CreateImage(0, 24, 24)

; ----- Create standard ToolBar
CreateToolBar(0, WindowID(0))

; ----- Change size of ToolBar to larger icons
CocoaMessage(0, ToolBarID(0), "setSizeMode:", #NSToolbarSizeModeRegular)

; ----- Add button with empty dummy image to ToolBar
ToolBarImageButton(0, ImageID(0))

; ----- Get item object of ToolBar button
ItemArray = CocoaMessage(0, ToolBarID(0), "items")
ToolbarItem = CocoaMessage(0, ItemArray, "objectAtIndex:", 0)

; ----- Replace ToolBar button with search field
CocoaMessage(0, ToolbarItem, "setView:", SearchField)

; ----- Change ToolBar button size to search field size
ItemSize\width = 150
ItemSize\height = 23
CocoaMessage(0, ToolbarItem, "setMaxSize:@", @ItemSize)
CocoaMessage(0, ToolbarItem, "setMinSize:@", @ItemSize)

; ----- Make window visible now in order to hide previous ToolBar modification
HideWindow(0, #False)

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow

Re: Non-Standard item in a ToolBar

Posted: Sun Mar 23, 2014 6:21 pm
by Shardik
wilbert wrote:@Shardik,
I used setTag: to create some sort of universal option with a single callback but you don't need that in this case.
setTarget: is required when you use PureBasic gadgets to overwrite the target PB sets but if you create an object like a search field yourself, the target is nil by default meaning that it will follow the responder chain that ends with the app delegate.
So in this case the only thing you need to set on the control is the action with setAction: the other two can be omitted.
If you would add multiple custom objects like for example also a NSSegmentedControl and make the objects global variables, you could simply identify by the sender what object is calling the toolbar callback.
Thank you again, Wilbert, for your explanation and heads up. I have modified my above example.
wilbert wrote:One concern might be the .S in your import statements when using unicode.
I tested with .S on Snow Leopard and compiled with PB 5.22 x86 and x64 in ASCII and Unicode mode and was astonished that I didn't experience any problems. Therefore I didn't insert my ConvertToUTF8() workaround from previous code examples...

Re: Non-Standard item in a ToolBar

Posted: Sun Mar 23, 2014 6:35 pm
by wilbert
Shardik wrote:I tested with .S on Snow Leopard and compiled with PB 5.22 x86 and x64 in ASCII and Unicode mode and was astonished that I didn't experience any problems. Therefore I didn't insert my ConvertToUTF8() workaround from previous code examples...
It looks like the types argument is ignored and because you can define the selector yourself and don't need an existing one, it seems to work fine with both unicode and ascii.
In fact sel_registerName will only register the first character when unicode is enabled since the second byte for the unicode string is a 0 but in this case that doesn't matter much.
It just occurred to me using DataSection also enables a workaround for the p-ascii prototype bug so I added that as an extra example next to the workaround you already offered.

Re: Non-Standard item in a ToolBar

Posted: Sun Mar 23, 2014 9:07 pm
by ergrull0
You guys are really awesome! Thanks again for the Nth time!!! :P :P
What about the creation of that sort of "grouped" buttons gadget?

Re: Non-Standard item in a ToolBar

Posted: Sun Mar 23, 2014 9:27 pm
by wilbert
ergrull0 wrote:What about the creation of that sort of "grouped" buttons gadget?
Actually it's one button made of multiple segments. It's an instance of the NSSegmentedControl class.
The screenshot you posted in your first post most likely uses the NSSegmentStyleTexturedRounded style.
It can be accomplished more or less the same way as the search field.

Re: Non-Standard item in a ToolBar

Posted: Mon Mar 24, 2014 7:03 pm
by Shardik
ergrull0 wrote:What about the creation of that sort of "grouped" buttons gadget?

Code: Select all

EnableExplicit

#NSSegmentSwitchTrackingSelectOne = 0
#NSSegmentSwitchTrackingSelectAny = 1
#NSSegmentSwitchTrackingMomentary = 2

#NSToolbarSizeModeRegular = 1

ImportC ""
  sel_registerName(MethodName.S)
  class_addMethod(Class.I, Selector.I, Implementation.I, Types.S)
EndImport

Define GadgetID.I
Define ItemArray.I
Define ItemSize.NSSize
Define SegmentedCell.I
Define SegmentedControl.I
Define ToolbarItem.I

Procedure.S ConvertToUTF8(String.S)
  Protected UTF8String.S = Space(StringByteLength(String))
  PokeS(@UTF8String, String, -1, #PB_UTF8)
  ProcedureReturn UTF8String
EndProcedure

ProcedureC ToolBarCallback(Object.I, Selector.I, Sender.I)
  Shared SegmentedControl.I

  If Sender = SegmentedControl
    Debug "Selected segment = " + CocoaMessage(0, Sender, "selectedSegment")
  EndIf
EndProcedure

Procedure SetToolBarCallback(Callback.I, Control.I)
  Protected AppDelegate.I
  Protected DelegateClass.I

  AppDelegate = CocoaMessage(0, CocoaMessage(0, 0,
    "NSApplication sharedApplication"), "delegate")
  DelegateClass.I = CocoaMessage(0, AppDelegate, "class")
  class_addMethod(DelegateClass,
    sel_registerName(ConvertToUTF8("forwardAction:")), Callback,
    ConvertToUTF8("v@:@"))
  CocoaMessage(0, Control, "setAction:",
    sel_registerName(ConvertToUTF8("forwardAction:")))
EndProcedure

OpenWindow(0, 270, 100, 395, 220, "ToolBar with SegmentedControl",
  #PB_Window_SystemMenu | #PB_Window_Invisible)

FrameGadget(  0,  10,  10, 170, 185, "Button style")
OptionGadget( 1,  20,  35, 150,  22, "Rounded")
OptionGadget( 2,  20,  60, 150,  22, "Textured + rounded")
OptionGadget( 3,  20,  85, 150,  22, "Rounded rectangle")
OptionGadget( 4,  20, 110, 150,  22, "Textured + squared")
OptionGadget( 5,  20, 135, 150,  22, "Capsule")
OptionGadget( 6,  20, 160, 150,  22, "Small + squared")
SetGadgetState(1, #True)

FrameGadget(  7, 190,  10, 195, 115, "Selection behaviour")
OptionGadget( 8, 200,  35, 175,  22, "Only one always selected")
OptionGadget( 9, 200,  60, 175,  22, "Multiple always selected")
OptionGadget(10, 200,  85, 175,  22, "One momentarily selected")
SetGadgetState(8, #True)

; ----- Define SegmentedControl
SegmentedControl = CocoaMessage(0, 0, "NSSegmentedControl new")
CocoaMessage(0, SegmentedControl,
  "setSegmentCount:", 4)
CocoaMessage(0, SegmentedControl,
  "setImage:", CocoaMessage(0, 0, "NSImage imageNamed:$", @"NSGoLeft"),
  "forSegment:", 0)
CocoaMessage(0, SegmentedControl,
  "setImage:", CocoaMessage(0, 0, "NSImage imageNamed:$", @"NSGoRight"),
  "forSegment:", 1)
CocoaMessage(0, SegmentedControl,
  "setImage:", CocoaMessage(0, 0, "NSImage imageNamed:$", @"NSQuickLook"),
  "forSegment:", 2)
CocoaMessage(0, SegmentedControl,
  "setImage:", CocoaMessage(0, 0, "NSImage imageNamed:$", @"NSAction"),
  "forSegment:", 3)
CocoaMessage(0, SegmentedControl,
  "setSelected:", #YES,
  "forSegment:", 2)
SetToolBarCallback(@ToolBarCallback(), SegmentedControl)

SegmentedCell = CocoaMessage(0, SegmentedControl, "cell")

; ----- Create empty dummy image
CreateImage(0, 24, 24)

; ----- Create standard ToolBar
CreateToolBar(0, WindowID(0))

; ----- Change size of ToolBar to larger icons
CocoaMessage(0, ToolBarID(0), "setSizeMode:", #NSToolbarSizeModeRegular)

; ----- Add button with empty dummy image to ToolBar
ToolBarImageButton(0, ImageID(0))

; ----- Get item object of ToolBar button
ItemArray = CocoaMessage(0, ToolBarID(0), "items")
ToolbarItem = CocoaMessage(0, ItemArray, "objectAtIndex:", 0)

; ----- Replace ToolBar button with SegmentedControl
CocoaMessage(0, ToolbarItem, "setView:", SegmentedControl)

; ----- Change ToolBar button size to SegmentedControl's size
ItemSize\width = 150
ItemSize\height = 24
CocoaMessage(0, ToolbarItem, "setMaxSize:@", @ItemSize)
CocoaMessage(0, ToolbarItem, "setMinSize:@", @ItemSize)

; ----- Make window visible now in order to hide previous ToolBar modification
HideWindow(0, #False)

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
    Case #PB_Event_Gadget
      GadgetID = EventGadget()

      Select GadgetID
        Case 1 To 6
          CocoaMessage(0, SegmentedControl, "setSegmentStyle:", GadgetID)
        Case 8 To 10
          CocoaMessage(0, SegmentedCell,
            "setTrackingMode:", GadgetID - 8)
      EndSelect
  EndSelect
ForEver
Update: I have further expanded the example by also allowing to change the selection behaviour of the buttons. It's now possible to

- Keep only one button permanently selected
- Keep multiple buttons permanently selected
- Cancel the selection as soon as the button is released

Thank you, Wilbert, for sending me a PM with the hint to also consider setTrackingMode: :wink:

Re: Non-Standard item in a ToolBar

Posted: Tue Mar 25, 2014 10:48 pm
by ergrull0
Thanks again Shardik! May I ask you how do you came up with this code? I mean...what does this piece of code do and how does it work?

Code: Select all

ProcedureC ToolBarCallback(Object.I, Selector.I, Sender.I)
  Shared SegmentedControl.I

  If Sender = SegmentedControl
    Debug "Selected segment = " + CocoaMessage(0, Sender, "selectedSegment")
  EndIf
EndProcedure

Procedure SetToolBarCallback(Callback.I, Control.I)
  Protected AppDelegate.I
  Protected DelegateClass.I

  AppDelegate = CocoaMessage(0, CocoaMessage(0, 0,
    "NSApplication sharedApplication"), "delegate")
  DelegateClass.I = CocoaMessage(0, AppDelegate, "class")
  class_addMethod(DelegateClass,
    sel_registerName(ConvertToUTF8("forwardAction:")), Callback,
    ConvertToUTF8("v@:@"))
  CocoaMessage(0, Control, "setAction:",
    sel_registerName(ConvertToUTF8("forwardAction:")))
EndProcedure

Re: Non-Standard item in a ToolBar

Posted: Wed Mar 26, 2014 6:53 pm
by Shardik
ergrull0 wrote:May I ask you how do you came up with this code? I mean...what does this piece of code do and how does it work?
When using Mac controls which are not implemented by PureBasic (because they are only available on the Mac and hence not cross-plattform like the SegmentedControl), you need to evaluate user actions from these controls. When pressing a button of a SegmentedControl, it's not possible to evaluate that button press in PB's event loop because PB simply doesn't know a SegmentedControl or search field. Therefore you have to setup a callback which is called whenever one of your Mac-specific controls is activated. The procedure SetToolBarCallback() in principal sets up the callback ToolBarCallback(). ToolBarCallback() is called from MacOS whenever one of your special controls is activated by the program's user.

Of course it's possible in this callback to use a PostEvent() command to generate a PB event which can be detected by the normal PB event loop, so that most of your programming logic can still be done in PB's event loop...