ホームページ開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでツールバーを実装する・Big Surの新機能を試す

 Xojo / Real Studio Trial and Error

CocoaのDeclareでツールバーを実装する・Big Surの新機能を試す

目次
 はじめに

 以下は、Xojo Cocoaビルドについての話題です。

 NSToolbarのBig Sur対応について、試してみました。

 なお検証には、Xojo 2021 Release 1を用いています。(Mac mini 2018 + macOS 11.2.3 Big Sur)


 方針

 Big Surでのツールバーについては、そうこうしているうちに、Xojo自身が2021 Release 1で一体型に対応しました。
(Xojo, Inc.からのメールに、macOS 11 SDK now used for both Intel and Apple Silicon buildsとあったのが関係しているのかな。)
 Cocoaベースのツールバーでは、何もしなくても一体型で表示されます。

 なので、一体型だけならわざわざレポートすることもないのですが、Big Surツールバーの特徴として、
 1. ウィンドウをマルチペインで表示した場合、
  a. 左ペイン(サイドバー)が、ツールバー領域も含めたウィンドウの高さ一杯まで拡大可能に。(以下、フルハイト)
  b. ペインごとにアイテムのセットを持つことができ、それらがペインの移動に追随する。この時、分割線(以下、セパレーター)を表示できる。
 2.アイコンのみの表示とすると、ツールバーアイテムがラージサイズになる。
といった点が挙げられると思います。

 参考サイト(1):What’s New in macOS - macOS - Human Interface Guidelines - Apple Developer(Windows and Viewsの項)

 今回は、このあたりを探ってみることにします。
 さて、具体例として代表的なのが(参考サイト(1)でも取り上げられているように)メール.appなので、試しにもどきが作れるかやってみたのが以下の画面です。
S Shot1
注:サイドバー領域には、表示状況確認用としてNSOutlineViewを貼り付けてありますが、以下の実装には含まれていません。
今回の話題とは直接の関係がないことと、理解がまだ不十分なためですが、何かあれば別稿とするかも。
 まずは3ペイン化ですが、これはNSSplitViewで行います。
 XcodeではNSSplitViewの配置や設定はインターフェースビルダーで行うのが一般的なようですが、Xojoでは使えませんので、例によってコードで記述します。
 この時、以前は省略できたので作リませんでしたが、ViewControllerも生成します。
(ViewControllerはウィンドウにセットしますが、実験で確認したら既存のViewControllerはなかったので、弊害はなさそう。)

 サイドバーのフルハイト化はウィンドウの属性で、styleMaskNSWindowStyleMaskFullSizeContentViewを追加します。
 フルハイト化はサイドバーのViewをツールバー領域まで伸ばしているのではなく、(パラメーター名が示しているように)ウィンドウ全域をContentView化して、ツールバーもその中に埋め込む、ということのようです。
(そのため、ウィンドウ高さが小さくなるが、これはXcodeも同様。ウィンドウの下端は動かずに上端が下がるのは座標系が関係している?)

 一方、ツールバーのセパレーターは、ツールバーアイテムとして指定します。使うのはNSTrackingSeparatorToolbarItemです。
 生成時にSplitViewを指定することで、位置がViewの動きに連動する、ということのようです。
 サーチフィールドは、新しく追加されたNSSearchToolbarItemを使ってみます。といっても、従来のNSToolbarItemを置き換えるだけなので簡単です。
 その他のツールバーアイテムについては、コントロールのラージサイズ化がありますが、参考サイト(1)によるとautomatically use the large control sizeなので、ひとまず何もせずに様子を見ます。

 あと、ツールバーとは直接関係ありませんが、アイコンには、Big Surからイメージとして利用できるようになったSF Symbolsを使ってみることにします。
 前述のメールにはSF Symbol toolbar icons on Big Surともあったので、標準的な利用法があるのかと思って探したのですが、見つかりませんでした。
(公式フォーラムには「そりゃ、XojoのIDEのことだよ」ってトピックがありましたが、そういうことなの?)
 なので、CocoaのAPIで実装することにしました。
・これは実装してから判明したことですが、アイコンに従来と同じく(例えば)PNG形式の画像を使うと、アイテムがラージ化してもアイコンサイズは変わりませんでした。どうも、Big SurではSF Symbolsとセットにするのが作法?のようです。
・当初は、svgファイルを、SF Symbols.appのファイル>カスタム・シンボル・テンプレートを書き出す...でExport後、プロジェクトにドラッグ&ドロップしていたのですが、既存のものを利用する場合は名前を指定するだけでいけました。(注:おわりに参照)
 残りの(主な)方針は、以下の通りとしました。

 Xojoでの実装
【ソースコードのコピー&ペーストについて】
・ソースコード(グレー背景部分の全文)をコピーし、指定のオブジェクトにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
・ペーストはオブジェクトに行って下さい。オブジェクト内のEvent Handlers/Methods/Properties等にペーストしても、うまくいかない場合があります。
・それでもペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. Xojoで新規プロジェクトを作成(Window1のWidthを680に変更)
  2. 以下をWindow1にペースト
    Sub Open() Handles Open
      // SplitViewとToolbar生成
      Dim sview As Ptr = InitSplitView()
      InitToolbar(sview)
      
      // ウィンドウジオメトリー調整
      me.Top=me.Top-21
    End Sub
    
  3. 以下をWindow1にペースト
    Protected Function ClrCtoPtr(clrC As Color) As Ptr
      Dim r, g, b As CGFloat
      
      // 0〜255 を 0.0〜1.0 にマッピング
      r=clrC.Red/255
      g=clrC.Green/255
      b=clrC.Blue/255
      
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // カラーの取得
      Dim clr As Ptr = NSClassFromString("NSColor")
      
      // RGB値からカラーを生成
      Declare Function colorWithCalibrate Lib "Cocoa" Selector "colorWithCalibratedRed:green:blue:alpha:" (receiver As Ptr, red As CGFloat, green As CGFloat, blue As CGFloat, alpha As CGFloat) As Ptr
      clr = colorWithCalibrate(clr, r, g, b, 1.0)  // alpha値は常に1.0
      
      // カラーを返す
      return clr
    End Function
    
  4. 以下をWindow1にペースト
    Protected Function InitSplitView() As Ptr
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
      Declare Function initWithNibName Lib "Cocoa" Selector "initWithNibName:bundle:" (receiver As Ptr, name As Ptr, bundle As Ptr) As Ptr
      Declare Function initWithFrame Lib "Cocoa" Selector "initWithFrame:" (receiver As Ptr, identifier As NSRect) As Ptr
      Declare Sub setView Lib "Cocoa" Selector "setView:" (receiver As Ptr, view As Ptr)
      Declare Sub insertSplitViewItem Lib "Cocoa" Selector "insertSplitViewItem:atIndex:" (receiver As Ptr, item As Ptr, idx As Integer)
      
      // SplitViewController生成
      Dim vcntP As Ptr = NSClassFromString("NSSplitViewController")
      vcntP = initWithNibName(alloc(vcntP), nil, nil)
      
      // SplitView生成
      Dim viewP As Ptr = NSClassFromString("NSSplitView")
      viewP = initWithFrame(alloc(viewP), NSMakeRect(0,0,600,400))
      
      // 分割スタイルは縦型
      Declare Sub setVertical Lib "Cocoa" Selector "setVertical:" (receiver As Ptr, flg As Boolean)
      setVertical(viewP, true)
      
      // デバイダー形状は薄型(NSSplitViewDividerStyleThick = 1,NSSplitViewDividerStyleThin = 2)
      Declare Sub setDividerStyle Lib "Cocoa" Selector "setDividerStyle:" (receiver As Ptr, flg As Integer)
      setDividerStyle(viewP, 2)
      
      // デバイダーカラーはグレー
      Declare Sub setDividerColor Lib "Cocoa" Selector "setDividerColor:" (receiver As Ptr, clr As Ptr)
      setDividerColor(viewP, ClrCtoPtr(RGB(192,192,192)))
      
      // SplitViewControllerにSplitViewをセット
      Declare Sub setSplitView Lib "Cocoa" Selector "setSplitView:" (receiver As Ptr, view As Ptr)
      setSplitView(vcntP, viewP)
      
      // SplitViewに配置するViewのController生成(サイドバー用)
      Dim vcntS As Ptr = NSClassFromString("NSViewController")
      vcntS = initWithNibName(alloc(vcntS), nil, nil)
      
      // SplitViewに配置するView生成(サイドバー用)
      Dim viewS As Ptr = NSClassFromString("NSView")
      viewS = initWithFrame(alloc(viewS), NSMakeRect(0,0,200,400))
      
      // ViewControllerにViewをセット(サイドバー用)
      setView(vcntS, viewS)
      
      // SplitViewに配置するViewのController生成(リスト用)
      Dim vcntL As Ptr = NSClassFromString("NSViewController")
      vcntL = initWithNibName(alloc(vcntL), nil, nil)
      
      // SplitViewに配置するView生成(リスト用)
      Dim viewL As Ptr = NSClassFromString("NSView")
      viewL = initWithFrame(alloc(viewL), NSMakeRect(0,0,240,400))
      
      // ViewControllerにViewをセット(リスト用)
      setView(vcntL, viewL)
      
      // SplitViewに配置するViewのController生成(デフォルト用)
      Dim vcntD As Ptr = NSClassFromString("NSViewController")
      vcntD = initWithNibName(alloc(vcntD), nil, nil)
      
      // SplitViewに配置するView生成(デフォルト用)
      Dim viewD As Ptr = NSClassFromString("NSView")
      viewD = initWithFrame(alloc(viewD), NSMakeRect(0,0,240,400))
      
      // ViewControllerにViewをセット(デフォルト用)
      setView(vcntD, viewD)
      
      // SplitViewItem生成(サイドバー用)
      Dim itemS As Ptr = NSClassFromString("NSSplitViewItem")
      Declare Function sidebarWithViewController Lib "Cocoa" Selector "sidebarWithViewController:" (receiver As Ptr, cntl As Ptr) As Ptr
      itemS = sidebarWithViewController(itemS, vcntS)
      
      // SplitViewControllerにSplitViewItemを追加(サイドバー用)
      insertSplitViewItem(vcntP, itemS, 0)
      
      // SplitViewItem生成(リスト用)
      Dim itemL As Ptr = NSClassFromString("NSSplitViewItem")
      Declare Function contentListWithViewController Lib "Cocoa" Selector "contentListWithViewController:" (receiver As Ptr, cntl As Ptr) As Ptr
      itemL = contentListWithViewController(itemL, vcntL)
      
      // SplitViewControllerにSplitViewItemを追加(リスト用)
      insertSplitViewItem(vcntP, itemL, 1)
      
      // SplitViewItem生成(デフォルト用)
      Dim itemD As Ptr = NSClassFromString("NSSplitViewItem")
      Declare Function splitViewItemWithViewController Lib "Cocoa" Selector "splitViewItemWithViewController:" (receiver As Ptr, cntl As Ptr) As Ptr
      itemD = splitViewItemWithViewController(itemD, vcntD)
      
      // SplitViewControllerにSplitViewItemを追加(デフォルト用)
      insertSplitViewItem(vcntP, itemD, 2)
      
      // ウィンドウにSplitViewControllerをセット
      Declare Sub setContentViewController Lib "Cocoa" Selector "setContentViewController:" (receiver As Integer, view As Ptr)
      setContentViewController(me.Handle, vcntP)
      
      // ウィンドウの現在のStyleMaskを取得して、ContentViewのフルサイズ化オプションを追加して戻す(NSWindowStyleMaskFullSizeContentView = 1 << 15)
      Declare Function styleMask Lib "Cocoa" Selector "styleMask" (receiver As Integer) As Integer
      Declare Sub setStyleMask Lib "Cocoa" Selector "setStyleMask:" (receiver As Integer, mask As Integer)
      setStyleMask(me.Handle, styleMask(me.Handle)+Bitwise.ShiftLeft(1,15))
      
      return viewP
    End Function
    
  5. 以下をWindow1にペースト
    Protected Sub InitToolbar(sview As Ptr)
      // Toolbarクラスの生成(ツールバーをセットするウィンドウ、Actionを受け取るメソッド、SplitViewを引数で指定する)
      Dim tbc As CocoaToolbar = new CocoaToolbar(self, AddressOf ToolbarItemClicked, sview)
      
      // ツールバースタイルのセット
      Declare Sub setToolbarStyle Lib "AppKit" Selector "setToolbarStyle:" (receiver As Integer, cnt As Integer)
      setToolbarStyle(me.Handle, 0)  // 0,3=Unify 1=Previous
      
      // ウィンドウのサブタイトルセット
      Declare Sub setSubtitle Lib "AppKit" Selector "setSubtitle:" (receiver As Integer, txt As CFStringRef)
      setSubtitle(me.Handle, "サブタイトル")
    End Sub
    
  6. 以下をWindow1にペースト
    Private Sub ToolbarItemClicked(sender As Ptr)
      Declare Function description Lib "Cocoa" Selector "description" (receiver As Ptr) As CFStringRef
      Dim name As String = description(sender)
      if InStrB(name,"NSSearchField")>0 then  // サーチフィールド
        
        Declare Function stringValue Lib "Cocoa" Selector "stringValue" (receiver As Ptr) As CFStringRef
        messageBox stringValue(sender)
        
      elseif InStrB(name,"NSSegmentedControl")>0 then  // セグメンテッドコントロール
        
        Declare Function selectedSegment Lib "Cocoa" Selector "selectedSegment" (receiver As Ptr) As Integer
        Declare Function cell Lib "Cocoa" Selector "cell" (receiver As Ptr) As Ptr
        Declare Function tagForSegment Lib "Cocoa" Selector "tagForSegment:" (receiver As Ptr, seg As Integer) As Integer
        Dim tagno As Integer = tagForSegment(cell(sender), selectedSegment(sender))
        
        if tagno >= 600 and tagno < 700 then  // 1セグメントコントロール(通常のアイテム代わり)
          Declare Sub setSelected Lib "Cocoa" Selector "setSelected:forSegment:" (receiver As Ptr, selected As Boolean, no As Integer)
          setSelected(sender, false, 0)  // ボタンを押下状態から戻す
          select case tagno
          case 601  // 新規
            messageBox "新規"
          case 602  // 保存
            messageBox "保存"
          end select
        end if
        
      else
        messageBox "No Define"
      end if
    End Sub
    
  7. 新規クラスを作成(名前は、ここでは「CocoaToolBar」とした。)
  8. 以下をCocoaToolBarにペースト(できなければDelegateに、Delegate Name:ActionDelegate、Parameters:sender As Ptr、を追加)
    Private Sub ActionDelegate(sender As Ptr)
    
  9. 以下をCocoaToolBarにペースト
    Public Sub Constructor(win As Window, action As ActionDelegate, sview As Ptr)
      // インスタンス側でActionを受け取るメソッドを登録
      ActionHandler = action
      
      // SplitViewを保持(セパレーター生成時に使用)
      SplitView = sview
      
      // ツールバーの生成
      SetToolbar(win)
    End Sub
    
  10. 以下をCocoaToolBarにペースト
    Private Sub SetToolbar(win As Window)
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // ツールバーの初期化
      Dim toolbar1 As Ptr = NSClassFromString("NSToolbar")
      Declare Function toolbarAlloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr
      toolbar1 = toolbarAlloc(toolbar1)
      Declare Function toolbarInit Lib "Cocoa" Selector "initWithIdentifier:" (receiver As Ptr, identifier As CFStringRef) As Ptr
      toolbar1 = toolbarInit(toolbar1, "MySampleToolbar")
      
      // 各種パラメータの設定
      Declare Sub setAllowsUserCustomization Lib "Cocoa" Selector "setAllowsUserCustomization:" (receiver As Ptr, value As Boolean)
      setAllowsUserCustomization(toolbar1, true)
      Declare Sub setAutosavesConfiguration Lib "Cocoa" Selector "setAutosavesConfiguration:" (receiver As Ptr, value As Boolean)
      setAutosavesConfiguration(toolbar1, true)
      Declare Sub setDisplayMode Lib "Cocoa" Selector "setDisplayMode:" (receiver As Ptr, value As Integer)
      setDisplayMode(toolbar1, 1)  // 0=Default, 1=Icon & Text, 2=Icon Only, 3=Text Only
      
      // Delegateの設定
      Declare Sub setDelegate Lib "Cocoa" Selector "setDelegate:" (receiver As Ptr, id As Ptr)
      setDelegate(toolbar1, makeDelegate())
      
      // ウィンドウにツールバーを登録
      Declare Sub setToolbar Lib "Cocoa" Selector "setToolbar:" (receiver As Integer, toolbar As Ptr)
      setToolbar(win.Handle, toolbar1)
      
      // 後処理
      Declare Sub release Lib "Cocoa" Selector "release" (toolbar As Ptr)
      release(toolbar1)
    End Sub
    
  11. 以下をCocoaToolBarにペースト
    Private Shared Sub actionEvent(id As Ptr, sel As Ptr, sender As Ptr)
      // インスタンス側でActionを受け取るメソッドをレイズする
      ActionHandler.Invoke(sender)
    End Sub
    
  12. 以下をCocoaToolBarにペースト
    Private Shared Sub controlTextViewDoCommandBySelector(id As Ptr, sel As Ptr, control As Ptr, textView As Ptr, command As Ptr)
      Dim mb As MemoryBlock
      mb = command
      if mb.CString(0) = "insertNewline:" then  // retunキーが押された
        
        // インスタンス側でActionを受け取るメソッドをレイズする
        ActionHandler.Invoke(control)
        
      end if
    End Sub
    
  13. 以下をCocoaToolBarにペースト
    Private Shared Function makeDelegate() As Ptr
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      // Declare宣言
      Declare Function objc_allocateClassPair Lib "Cocoa" (superclass As Ptr, name As CString, extraBytes As Integer) as Ptr
      Declare Sub objc_registerClassPair Lib "Cocoa" (cls As Ptr)
      Declare Function class_addMethod Lib "Cocoa" (cls As Ptr, name As Ptr, imp As Ptr, types As CString) As Boolean
      
      // クラス名をmyNSToolbarDelegate(名前は任意。少なくとも今回のケースでは参照されない。)、メタクラス名をNSObjectにして、生成
      Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), "myNSToolbarDelegate", 0)
      // ランタイムに登録(参照を可能とするため)
      objc_registerClassPair newClassId
      // Delegateの対象となるメソッドを追加(toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:をXojo側で用意したtoolbarItemForItemIdentifierWillBeInsertedIntoToolbarメソッドで受け取る。)
      if not class_addMethod (newClassId, NSSelectorFromString("toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:"), AddressOf toolbarItemForItemIdentifierWillBeInsertedIntoToolbar, "@@:@@c") then
        msgBox "error."
        return nil
      end if
      // Delegateの対象となるメソッドを追加(toolbarAllowedItemIdentifiers:をXojo側で用意したtoolbarAllowedItemIdentifiersメソッドで受け取る。)
      if not class_addMethod (newClassId, NSSelectorFromString("toolbarAllowedItemIdentifiers:"), AddressOf toolbarAllowedItemIdentifiers, "@@:@") then
        msgBox "error."
        return nil
      end if
      // Delegateの対象となるメソッドを追加(toolbarDefaultItemIdentifiers:をXojo側で用意したtoolbarDefaultItemIdentifiersメソッドで受け取る。)
      if not class_addMethod (newClassId, NSSelectorFromString("toolbarDefaultItemIdentifiers:"), AddressOf toolbarDefaultItemIdentifiers, "@@:@") then
        msgBox "error."
        return nil
      end if
      // Delegateの対象となるメソッドを追加(toolbarSelectableItemIdentifiers:をXojo側で用意したtoolbarSelectableItemIdentifiersメソッドで受け取る。)
      if not class_addMethod (newClassId, NSSelectorFromString("toolbarSelectableItemIdentifiers:"), AddressOf toolbarSelectableItemIdentifiers, "@@:@") then
        msgBox "error."
        return nil
      end if
      
      // 上記で生成したクラスのインスタンスを作成
      Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
      Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr
      DelegateInstance = init(alloc(newClassId))
      
      // インスタンスを返す
      return DelegateInstance
    End Function
    
  14. 以下をCocoaToolBarにペースト
    Private Shared Function makeDelegateTextView(name As String) As Ptr
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      // Declare宣言
      Declare Function objc_allocateClassPair Lib "Cocoa" (superclass As Ptr, name As CString, extraBytes As Integer) as Ptr
      Declare Sub objc_registerClassPair Lib "Cocoa" (cls As Ptr)
      Declare Function class_addMethod Lib "Cocoa" (cls As Ptr, name As Ptr, imp As Ptr, types As CString) As Boolean
      
      // 既にインスタンス作成済なら、それを返す
      if SearchDelegateInstance <> nil then
        return SearchDelegateInstance
      end if
      
      // クラス名を引数のname(名前は任意だが、重複を避けるためにアイテムのIdentifierにしている。)、メタクラス名をNSObjectにして、生成
      Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), name, 0)
      // ランタイムに登録(参照を可能とするため)
      objc_registerClassPair newClassId
      // Delegateの対象となるメソッドを追加(control:textView:doCommandBySelector:をXojo側で用意したcontrolTextViewDoCommandBySelectorメソッドで受け取る。)
      if not class_addMethod (newClassId, NSSelectorFromString("control:textView:doCommandBySelector:"), AddressOf controlTextViewDoCommandBySelector, "v@:@@@") then
        msgBox "error."
        return nil
      end if
      
      // 上記で生成したクラスのインスタンスを作成
      Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
      Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr
      Dim delegateId As Ptr = init(alloc(newClassId))
      
      // インスタンスを保持
      SearchDelegateInstance = delegateId
      
      // インスタンスを返す
      return delegateId
    End Function
    
  15. 以下をCocoaToolBarにペースト
    Private Shared Function makeTarget(name As String) As Ptr
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      // Declare宣言
      Declare Function objc_allocateClassPair Lib "Cocoa" (superclass As Ptr, name As CString, extraBytes As Integer) as Ptr
      Declare Sub objc_registerClassPair Lib "Cocoa" (cls As Ptr)
      Declare Function class_addMethod Lib "Cocoa" (cls As Ptr, name As Ptr, imp As Ptr, types As CString) As Boolean
      
      // 既にインスタンス作成済なら、それを返す
      if TargetInstance <> nil then
        return TargetInstance
      end if
      
      // クラス名を引数のname(名前は任意だが、重複を避けるためにアイテムのIdentifierにしている。)、メタクラス名をNSObjectにして、生成
      Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), name, 0)
      // ランタイムに登録(参照を可能とするため)
      objc_registerClassPair newClassId
      // Tarrgetに送られてきたActionの受け口となるメソッドを追加(action:をXojo側で用意したactionEventメソッドで受け取る。)
      if not class_addMethod (newClassId, NSSelectorFromString("action:"), AddressOf actionEvent, "@@:@") then
        msgBox "error."
        return nil
      end if
      // Delegateの対象となるメソッド(Protocol?)を追加(validateToolbarItem:をXojo側で用意したvalidateToolbarItemメソッドで受け取る。)
      if not class_addMethod (newClassId, NSSelectorFromString("validateToolbarItem:"), AddressOf validateToolbarItem, "c@:@") then
        msgBox "error."
        return nil
      end if
      
      // 上記で生成したクラスのインスタンスを作成
      Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
      Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr
      Dim targetId As Ptr = init(alloc(newClassId))
      
      // インスタンスを保持
      TargetInstance = targetId
      
      // インスタンスを返す
      return targetId
    End Function
    
  16. 以下をCocoaToolBarにペースト
    Private Shared Function setToolItemSCbutton1(itemIdentifier As String, label As String, labelL As String, tooltip As String, icon As String, extn As String, tag As Integer) As Ptr
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr
      
      // アイコン画像の取得(SF Symbols)
      Dim image1 As Ptr = NSClassFromString("NSImage")
      Declare Function imageWithSystemSymbolName Lib "Cocoa" Selector "imageWithSystemSymbolName:accessibilityDescription:" (receiver As Ptr, name As CFStringRef, desc As CFStringRef) As Ptr
      image1 = imageWithSystemSymbolName(image1, icon, "")
      
      // セグメンテッドコントロール初期化
      Dim segment1 As Ptr = NSClassFromString("NSSegmentedControl")
      segment1 = alloc(segment1)
      Declare Function initWithFrame Lib "Cocoa" Selector "initWithFrame:" (receiver As Ptr, identifier As NSRect) As Ptr
      segment1 = initWithFrame(segment1, NSMakeRect(0,0,40,24))
      
      // セグメント数/アイコン/幅/選択状態/タグ設定
      Declare Sub setSegmentCount Lib "Cocoa" Selector "setSegmentCount:" (receiver As Ptr, cnt As Integer)
      setSegmentCount(segment1, 1)
      Declare Sub setImage Lib "Cocoa" Selector "setImage:forSegment:" (receiver As Ptr, image As Ptr, no As Integer)
      setImage(segment1, image1, 0)
      Declare Sub setWidth Lib "Cocoa" Selector "setWidth:forSegment:" (receiver As Ptr, w As CGFloat, no As Integer)
      setWidth(segment1, 36, 0)
      Declare Sub setSelected Lib "Cocoa" Selector "setSelected:forSegment:" (receiver As Ptr, selected As Boolean, no As Integer)
      setSelected(segment1, false, 0)  // 非選択
      Declare Function cell Lib "Cocoa" Selector "cell" (receiver As Ptr) As Ptr
      Declare Sub setTag Lib "Cocoa" Selector "setTag:forSegment:" (receiver As Ptr, tag As Integer, no As Integer)
      setTag(cell(segment1), tag, 0)  // タグだけ判別するので、同じActionメソッドを使用するセグメント間で固有の数値にする
      
      // アイテム初期化
      Dim toolbarItem As Ptr = NSClassFromString("NSToolbarItem")
      toolbarItem = alloc(toolbarItem)
      Declare Function initWithItemIdentifier Lib "Cocoa" Selector "initWithItemIdentifier:" (receiver As Ptr, identifier As CFStringRef) As Ptr
      toolbarItem = initWithItemIdentifier(toolbarItem, itemIdentifier)
      
      // ラベル/ツールチップ設定
      Declare Sub setLabel Lib "Cocoa" Selector "setLabel:" (receiver As Ptr, label As CFStringRef)
      setLabel(toolbarItem, labelL)
      Declare Sub setPaletteLabel Lib "Cocoa" Selector "setPaletteLabel:" (receiver As Ptr, label As CFStringRef)
      setPaletteLabel(toolbarItem, label)
      Declare Sub setToolTip Lib "Cocoa" Selector "setToolTip:" (receiver As Ptr, text As CFStringRef)
      setToolTip(toolbarItem, tooltip)
      
      // アイテムのビューにセグメンテッドコントロールを設定
      Declare Sub setView Lib "Cocoa" Selector "setView:" (receiver As Ptr, actionTarget As Ptr)
      setView(toolbarItem, segment1)
      
      // Target/Action設定
      Declare Sub setTarget Lib "Cocoa" Selector "setTarget:" (receiver As Ptr, actionTarget As Ptr)
      setTarget(toolbarItem, makeTarget(itemIdentifier))  // Actionの受け口となるメソッドを定義
      Declare Sub setAction Lib "Cocoa" Selector "setAction:" (receiver As Ptr, actionEvent As Ptr)
      setAction(toolbarItem, NSSelectorFromString("action:"))
      
      // Hover Effectを有効に
      Declare Sub setBordered Lib "AppKit" Selector "setBordered:" (receiver As Ptr, flg As Boolean)
      setBordered(toolbarItem, true)
      
      // clean up
      Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
      release(segment1)
      
      // 生成したアイテムを返す
      return toolbarItem
    End Function
    
  17. 以下をCocoaToolBarにペースト
    Private Shared Function setToolItemSearch(itemIdentifier As String) As Ptr
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr
      
      // サーチフィールド初期化
      Dim search1 As Ptr = NSClassFromString("NSSearchField")
      search1 = alloc(search1)
      Declare Function initWithFrame Lib "Cocoa" Selector "initWithFrame:" (receiver As Ptr, identifier As NSRect) As Ptr
      search1 = initWithFrame(search1, NSMakeRect(0,0,150,24))
      
      // Delegate設定
      Declare Sub setDelegate Lib "Cocoa" Selector "setDelegate:" (receiver As Ptr, id As Ptr)
      setDelegate(search1, makeDelegateTextView(SearchToolbarItemIdentifier))  // retunキーが押された時をアクションとして認識する
      
      // アイテム初期化
      Dim toolbarItem As Ptr = NSClassFromString("NSSearchToolbarItem")  // 従来のNSToolbarItemから変更
      toolbarItem = alloc(toolbarItem)
      Declare Function initWithItemIdentifier Lib "Cocoa" Selector "initWithItemIdentifier:" (receiver As Ptr, identifier As CFStringRef) As Ptr
      toolbarItem = initWithItemIdentifier(toolbarItem, itemIdentifier)
      
      // ラベル/ツールチップ設定
      Declare Sub setLabel Lib "Cocoa" Selector "setLabel:" (receiver As Ptr, label As CFStringRef)
      setLabel(toolbarItem, "検索")
      Declare Sub setPaletteLabel Lib "Cocoa" Selector "setPaletteLabel:" (receiver As Ptr, label As CFStringRef)
      setPaletteLabel(toolbarItem, "検索")
      Declare Sub setToolTip Lib "Cocoa" Selector "setToolTip:" (receiver As Ptr, text As CFStringRef)
      setToolTip(toolbarItem, "書類を検索します")
      
      // サーチフィールドをクリック後、直ちに文字入力可能とするために必要(NSControlのメソッド)
      Declare Sub refusesFirstResponder Lib "Cocoa" Selector "setRefusesFirstResponder:" (receiver As Ptr, flg As Boolean)
      refusesFirstResponder(search1,true)
      
      // アイテムのビューにサーチフィールドを設定
      Declare Sub setView Lib "Cocoa" Selector "setView:" (receiver As Ptr, actionTarget As Ptr)
      setView(toolbarItem, search1)
      
      // Target/Actionは、NSSearchFieldCellのsearchButtonCellに対して設定する。
      Declare Function cell Lib "Cocoa" Selector "cell" (receiver As Ptr) As Ptr
      Dim cell1 As Ptr = cell(search1)
      Declare Sub setSendsWholeSearchString Lib "Cocoa" Selector "setSendsWholeSearchString:" (receiver As Ptr, flag As Boolean)
      setSendsWholeSearchString(cell1, false)  // false:虫眼鏡ボタンが有効に, true:虫眼鏡ボタンが無効に(この解釈でいいか?)
      Declare Function searchButtonCell Lib "Cocoa" Selector "searchButtonCell" (receiver As Ptr) As Ptr
      Dim searchButtonCell1 As Ptr = searchButtonCell(cell1)
      Declare Sub setTarget Lib "Cocoa" Selector "setTarget:" (receiver As Ptr, actionTarget As Ptr)
      setTarget(searchButtonCell1, makeTarget(itemIdentifier))  // Actionの受け口となるメソッドを定義
      Declare Sub setAction Lib "Cocoa" Selector "setAction:" (receiver As Ptr, actionEvent As Ptr)
      setAction(searchButtonCell1, NSSelectorFromString("action:"))
      
      // clean up
      Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
      release(search1)
      
      // 生成したアイテムを返す
      return toolbarItem
    End Function
    
  18. 以下をCocoaToolBarにペースト
    Private Shared Function setToolItemTrackSep() As Ptr
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // アイテム初期化
      Dim toolbarItem As Ptr = NSClassFromString("NSTrackingSeparatorToolbarItem")
      Declare Function trackingSeparatorToolbarItemWithIdentifier Lib "Cocoa" Selector "trackingSeparatorToolbarItemWithIdentifier:splitView:dividerIndex:" _
      (receiver As Ptr, identifier As CFStringRef, sview As Ptr, idex As Integer) As Ptr
      toolbarItem = trackingSeparatorToolbarItemWithIdentifier(toolbarItem, "TrackingSeparatorToolbarItemIdentifier", SplitView, 1)
      
      // 生成したアイテムを返す
      return toolbarItem
    End Function
    
  19. 以下をCocoaToolBarにペースト
    Private Shared Function toolbarAllowedItemIdentifiers(id As Ptr, sel As Ptr, toolbar As Ptr) As Ptr
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      Dim arr As Ptr = NSClassFromString("NSMutableArray")  // クラスメソッドなので、まずNSMutableArrayクラスを取得
      Declare Function getArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr  // Return Array*
      arr=getArray(arr)
      
      Declare Sub addObject Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As CFStringRef)
      addObject(arr, NewToolbarItemIdentifier)  // ユーザ定義アイテム
      addObject(arr, SaveToolbarItemIdentifier)  // ユーザ定義アイテム
      addObject(arr, SearchToolbarItemIdentifier)  // ユーザ定義アイテム
      
      addObject(arr, "NSToolbarSpaceItem")
      addObject(arr, "NSToolbarFlexibleSpaceItem")
      
      // リストを返す
      return arr
    End Function
    
  20. 以下をCocoaToolBarにペースト
    Private Shared Function toolbarDefaultItemIdentifiers(id as Ptr, sel as Ptr, toolbar As Ptr) As Ptr
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      Dim arr As Ptr = NSClassFromString("NSMutableArray")  // クラスメソッドなので、まずNSMutableArrayクラスを取得
      Declare Function getArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr  // Return Array*
      arr=getArray(arr)
      
      Declare Sub addObject Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As CFStringRef)
      addObject(arr, NewToolbarItemIdentifier)  // ユーザ定義アイテム
      addObject(arr, "TrackingSeparatorToolbarItemIdentifier")  // ユーザ定義アイテム
      addObject(arr, SaveToolbarItemIdentifier)  // ユーザ定義アイテム
      addObject(arr, "NSToolbarFlexibleSpaceItem")
      addObject(arr, SearchToolbarItemIdentifier)  // ユーザ定義アイテム
      
      // リストを返す
      return arr
    End Function
    
  21. 以下をCocoaToolBarにペースト
    Private Shared Function toolbarItemForItemIdentifierWillBeInsertedIntoToolbar(id as Ptr, sel as Ptr, toolbar As Ptr, itemIdentifier As CFStringRef, flag As Boolean) As Ptr
      if itemIdentifier = NewToolbarItemIdentifier then
        return setToolItemSCbutton1(itemIdentifier,"新規","新規","新規に作成します","doc.badge.plus","svg",601)  // 注:SF Symbolsでは拡張子は不要だが、これは従来の名残り。
        
      elseif itemIdentifier = SaveToolbarItemIdentifier then
        return setToolItemSCbutton1(itemIdentifier,"保存","保存","保存します","arrow.down.doc","svg",602)
        
      elseif itemIdentifier = SearchToolbarItemIdentifier then  // 検索
        return setToolItemSearch(itemIdentifier)
        
      elseif itemIdentifier = "TrackingSeparatorToolbarItemIdentifier" then  // セパレーター
        return setToolItemTrackSep()
        
      else
        return nil
      end if
    End Function
    
  22. 以下をCocoaToolBarにペースト
    Private Shared Function toolbarSelectableItemIdentifiers(id as Ptr, sel as Ptr, toolbar As Ptr) As Ptr
      // 選択(凹んだ)状態にしたいアイテムのリストを返す
      'msgBox "toolbarSelectableItemIdentifiers"
      
      return nil
    End Function
    
  23. 以下をCocoaToolBarにペースト
    Private Shared Function validateToolbarItem(id as Ptr, sel as Ptr, toolbarItem As Ptr) As Boolean
      // このメソッドはポーリングしているっぽいので、アクションを起こすことなく、プロパティを変化させるだけで直ちに反映される。
    End Function
    
  24. 以下をCocoaToolBarにペースト(できなければShared Propertyに、Name:ActionHandler、Type:ActionDelegate、を追加)
    Private Shared Property ActionHandler as ActionDelegate
    
  25. 以下をCocoaToolBarにペースト(できなければShared Propertyに、Name:DelegateInstance、Type:Ptr、を追加)
    Protected Shared Property DelegateInstance As Ptr
    
  26. 以下をCocoaToolBarにペースト(できなければShared Propertyに、Name:NewToolbarItemIdentifier、Type:String、Default:My New Toolbar Item、を追加)
    Private Shared Property NewToolbarItemIdentifier As String = "My New Toolbar Item"
    
  27. 以下をCocoaToolBarにペースト(できなければShared Propertyに、Name:SaveToolbarItemIdentifier、Type:String、Default:My Save Toolbar Item、を追加)
    Private Shared Property SaveToolbarItemIdentifier As String = "My Save Toolbar Item"
    
  28. 以下をCocoaToolBarにペースト(できなければShared Propertyに、Name:SplitView、Type:Ptr、を追加)
    Protected Shared Property SplitView As Ptr
    
  29. 以下をCocoaToolBarにペースト(できなければShared Propertyに、Name:TargetInstance、Type:Ptr、を追加)
    Protected Shared Property TargetInstance As Ptr = nil
    
  30. 他に、NSMakeRect(メソッド)、NSRect(構造体)が必要ですが、それらはmacoslibからコピーさせて頂きました。(別途モジュールを作る等してコピーする。)
    注)macoslibではNSRectのメンバーの型にSingleが割り当てられているが、64bitにも対応するため、CGFloatに書き換える。
 実行してみたところ、指定した新機能が動作することを確認しました。
S Shot2


 おわりに

 ペインの分割線の移動は、何もしなくても動作し、仕様通りツールバーアイテムも追随します。
(スペースが狭くなると移動が止まり、セパレーターではなく縦線アイコンに変わる。)
 サーチフィールドは領域が狭いと虫眼鏡だけが表示され、クリックするとフィールドが表示されるという、これも仕様通りの動作となっています。
(ちなみに、虫眼鏡だけに戻すには、非表示となっているボタン領域か、(表示されていれば)入力フィールドの×印をクリックします。)

 ツールバーアイテムのラージサイズ化も期待通りの表示となっています。アイコンの描画も滑らかです。
(一部のサイトで、11.0から新設されたNSControlSizeLargeが取り上げられているので、関係あるかとも思ったのですが、なくてもよさそうです。)
 ホバーエフェクトは、(少なくとも)2019の頃から、無効化されたツールバーアイテムでも作用する、という問題がありましたが、依然として同じ状況です。回避法は見つかっていません。
追記:その後、メニュー付のアイテムでは無効時にエフェクトが付かない、ことに気付きました。試しにメニュー無しのアイテムにも(セパレーターだけの)ダミーメニューを付加してみましたが、同様の結果が得られました。この場合、メニューは表示されず、通常のボタンとして機能します(ツールバーアイテムにターゲット/アクションを設定しているから?)。処理自体は冗長で、副作用についての検証も不十分ですが、突破口にはなるかも。(2021.06.08)
 SF Symbolsについては、フォントをインストールしていない独立した環境が手元にないので、常に参照できるかどうかは定かではありません。ちなみに、フォントをインストールしていない別ディスクを起動した場合は参照できています。カスタマイズしてみればより明確になるのでしょうが、生憎カスタマイズ環境も手元にないので、検証できていません。
追記2:SF Symbolsのカスタマイズについては、その後進展がありました。この件はこちら。(2021.08.06)
 Big Surから追加されたAPIも多く使っているので、それ以前のOSでも使いたい場合は、OSのバージョンを取得して分岐処理する必要があります。

 なお、今回は機能の確認のみが主眼となっており、実用にあたってはより詳細な設定等が必要になるかもしれません。
 また、諸般の事情(謎)により、メモリーリークのチェックができていないので、こちらも確認が必要かと思われます。


 お世話になったサイト

 貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)

 参考サイト(1):What’s New in macOS - macOS - Human Interface Guidelines - Apple Developer(Windows and Viewsの項)


 更新履歴

 2021.08.06 おわりに、に追記2を追加
 2021.06.08 おわりに、に追記を追加
 2021.04.20 新規作成


[Home]  [MacSoft]  [Donation]  [History]  [Privacy Policy]  [Affiliate Policy]