ホームページ開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでリッチテキストを扱う

 Xojo / Real Studio Trial and Error

CocoaのDeclareでリッチテキストを扱う

目次
 はじめに

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

 Xojoは標準でリッチテキストに対応していますが、以下の制約があります。
 一方、以前にも触れたとおり、XojoのTextAreaはNSTextView(NSScrollView)を継承している(と思われる)のですが、NSTextViewは上記には全て対応しています。なので、使えるか、調べてみました。

 なお検証には、Xojo 2015 Release 2.4を用いています。(Mac mini mid 2010 + OS X 10.11.1 El Capitan)


 方針

 リッチテキストの編集については、基本的にNSTextViewに任せる(注1参照)ので、実装はバーやパネルの呼び出し部分について行います。
 外部オブジェクトの追加については、今回は全てのフォルダアイテムを対象とします(注2参照)。この部分は自前で実装します。NSTextViewのDelegateを利用する方法もありそうですが、今回はXojoのDropObjectイベントを利用することにします。
 ファイルIOに関しては、読み込みはrtf/rtfdの双方、書き出しは(外部オブジェクトを含まなくても)rtfdのみとします。

 なお、パネルやファイル読み書きメソッドの呼び出しは、今回はツールバーを利用することとします。(メニューやボタンを利用したい場合は、適宜コードを書き換えて下さい。)
注1)NSTextViewは、直接リッチテキストを扱っている訳ではなく、内部的にはNSAttributedString(NSMutableAttributedString)という形式で持っているようです。
ただ、リッチテキストとの親和性は高いようで、ファイルIOも専用のメソッドが用意されていたりします。
参考サイト(1):添付書類付きリッチテキスト(.rtfd)の実態 - ザリガニが見ていた...。
注2)以下は当方での了解事項です。(単に理解が不足しているだけかもしれませんが…)
XojoのTextAreaは、何もしないと、全てのフォルダアイテムのドロップを受け付けて、そのパスを表示します。この時、DropObjectイベントはレイズされません。
AcceptFileDropを指定してやると、パスは表示されず、DropObjectイベントがレイズされますが、この場合も指定したファイルタイプだけでなく、全てのフォルダアイテムがドロップの対象となります。
ちなみに、Real Studio 2011r3で同じくAcceptFileDropを指定してやると、Carbnビルドでは指定したファイルタイプだけドロップを受け付け、CocoaビルドではXojoと同じ挙動となります。

 Xojoでの実装

 実装にあたっては、以下のサイトを参考にさせて頂きました。
 参考サイト(2):24 | 3月 | 2008 | QueueSoft Developing Diary
  1. Xojoで新規プロジェクトを作成
  2. ファイルタイプグループをプロジェクトに追加(Name:FileTypes1)し、一般的なファイルタイプの追加の中から、「application/rtf」を追加。更に以下を追加。
    表示名:application/rtfd オブジェクト名:Rtfd Macタイプ:fold Macクリエータ:MACS 拡張子:.rtfd UTI:com.apple.package
    
  3. TextAreaをWindow1に追加(Name:TextArea1)
    Styledプロパティはオンにしておく。(Defaultでそうなっている筈)
  4. Toolbarをプロジェクトに追加(Name:Toolbar1)後、以下のアイテムを追加。その後、Window1に追加(Name:Toolbar11)。
    ToolItem1(開く)/ToolItem2(保存)/ToolItem3(スペース)/ToolItem4(表)/ToolItem5(リンク)
  5. 以下をWindow1のOpenイベントに記述
    // NSTextViewの取得
    Dim pnt1 As Ptr
    declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
    pnt1 = documentView(TextArea1.Handle)
    
    // インスペクターバーを表示
    declare sub setUsesInspectorBar lib "Cocoa" selector "setUsesInspectorBar:" (receiver as Ptr, flag as Boolean)
    setUsesInspectorBar(pnt1, true)
    
    // ルーラーを表示
    declare sub setRulerVisible lib "Cocoa" selector "setRulerVisible:" (receiver as Ptr, flag as Boolean)
    setRulerVisible(pnt1, true)
    
    // インスペクターバー付加によるHeightの増加に、ウィンドウが追随しないことへの対策(なぜか、これでうまくいくみたい。)(2017.08.15)
    me.Height=me.Height
    
  6. 以下をWindow1のメソッドに追加
    メソッド名: OpenFile
    
    Dim dlg As OpenDialog
    Dim f As FolderItem
    
    dlg = New OpenDialog
    dlg.Filter = FileTypes1.Rtf + FileTypes1.Rtfd
    f = dlg.ShowModalWithin(self)
    if f = nil then
        return
    end if
    
    // パスの取得
    Dim filepath As String = f.NativePath
    
    // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    
    // TextAreaの取得
    Dim pnt1 As Ptr
    declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
    pnt1 = documentView(TextArea1.Handle)  // TextArea1.Handle = NSScrollVew*
    
    // RTFの読み込み(rtfもrtfdも、どちらも読める)
    declare sub readRTFDFromFile lib "Cocoa" selector "readRTFDFromFile:" (obj_id as ptr, path As CFStringRef)
    readRTFDFromFile(pnt1, filepath)
    
  7. 以下をWindow1のメソッドに追加
    メソッド名: SaveFile
    
    Dim dlg as New SaveAsDialog
    Dim f as FolderItem
    
    dlg.Filter = FileTypes1.Rtfd
    f=dlg.ShowModalWithin(self)
    if f = nil then
        return
    end if
    
    // パスの取得
    Dim filepath As String = f.NativePath
    
    // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    
    // TextAreaの取得
    Dim pnt1 As Ptr
    declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
    pnt1 = documentView(TextArea1.Handle)  // TextArea1.Handle = NSScrollVew*
    
    // RTFの書き出し(常にrtfd形式)
    declare sub writeRTFDToFile lib "Cocoa" selector "writeRTFDToFile:atomically:" (obj_id as ptr, path As CFStringRef, flag As Boolean)
    writeRTFDToFile(pnt1, filepath, true)
    
  8. 以下をTextArea1のOpenイベントに追加
    me.AcceptFileDrop("application/rtf")
    
    注)当方で確認した限りでは、上記だけで全てのフォルダアイテムがドロップ対象になります。

  9. 以下をTextArea1のDropObjectイベントに追加
    if Obj.FolderItemAvailable then
        
        // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
        Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
        
        // パスの取得
        Dim filepath As String = obj.FolderItem.NativePath
        
        // NSTextViewの取得
        Dim pnt1 As Ptr
        declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
        pnt1 = documentView(TextArea1.Handle)
        
        // NSTextStorageの取得
        Dim pnt2 As Ptr
        declare function textStorage lib "Cocoa" selector "textStorage" (obj_id as Ptr) As Ptr  // Return NSTextStorage*
        pnt2 = textStorage(pnt1)
        
        Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr
        
        Dim wrap As Ptr = NSClassFromString("NSFileWrapper")
        wrap = alloc(wrap)
        Declare Function pathInit Lib "Cocoa" Selector "initWithPath:" (receiver As Ptr, path As CFStringRef) As Ptr
        wrap = pathInit(wrap, filepath)
        
        Dim attachment As Ptr = NSClassFromString("NSTextAttachment")
        attachment = alloc(attachment)
        Declare Function wrapInit Lib "Cocoa" Selector "initWithFileWrapper:" (receiver As Ptr, wrapper As Ptr) As Ptr
        attachment = wrapInit(attachment, wrap)
        
        Dim attachChar As Ptr = NSClassFromString("NSAttributedString")
        Declare Function attributedStringWithAttachment Lib "Cocoa" Selector "attributedStringWithAttachment:" (receiver As Ptr, attachment As Ptr) As Ptr
        attachChar = attributedStringWithAttachment(attachChar, attachment)
        
        Dim attrString As Ptr = NSClassFromString("NSMutableAttributedString")
        attrString = alloc(attrString)
        Declare Function initWithString Lib "Cocoa" Selector "initWithAttributedString:" (receiver As Ptr, str As Ptr) As Ptr
        attrString = initWithString(attrString, pnt2)
        
        Declare Sub beginEditing Lib "Cocoa" Selector "beginEditing" (receiver As Ptr)
        beginEditing(attrString)
        
        Declare Sub insertAttributedString Lib "Cocoa" Selector "insertAttributedString:atIndex:" (receiver As Ptr, attachChar As Ptr, index As Integer)
        insertAttributedString(attrString, attachChar, TextArea1.SelStart)  // Xojo側からキャレット位置を取得。NSTextView側から取得した方がいいかも。
        
        Declare Sub endEditing Lib "Cocoa" Selector "endEditing" (receiver As Ptr)
        endEditing(attrString)
        
        // 画像を追加したストレージを書き戻す
        Declare Sub setAttributedString Lib "Cocoa" Selector "setAttributedString:" (receiver As Ptr, attrString As Ptr)
        setAttributedString(pnt2, attrString)
        
    end if
    
    注)上記方法でドロップは実現できますが、一部機能に制限がつくため、NSTextViewネイティブのメソッドを利用した方が良さそうです。詳細はこちらをご覧下さい。(2016.05.01)

  10. 以下をToolbar11のActionイベントに追加
    select case item.Name
    case "ToolItem1"  // 開く
        OpenFile()
    
    case "ToolItem2"  // 保存
        SaveFile()
    
    case "ToolItem4"  // 表
        // NSTextViewの取得
        Dim pnt1 As Ptr
        declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
        pnt1 = documentView(TextArea1.Handle)
        // 表ダイアログを表示
        declare sub orderFrontTablePanel lib "Cocoa" selector "orderFrontTablePanel:" (receiver as Ptr, sender as Ptr)
        orderFrontTablePanel(pnt1, nil)
    
    case "ToolItem5"  // リンク
        // NSTextViewの取得
        Dim pnt1 As Ptr
        declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
        pnt1 = documentView(TextArea1.Handle)    
        // リンクダイアログを表示
        declare sub orderFrontLinkPanel lib "Cocoa" selector "orderFrontLinkPanel:" (receiver as Ptr, sender as Ptr)
        orderFrontLinkPanel(pnt1, nil)
    
    end select
    

 実行してみたところ、リッチテキストの編集、ファイルの読み書きが機能することを確認しました。
S Shot1


 おわりに

 NSTextViewは他にも大量のメソッドが用意されてます。アンドゥ等の機能もある?ようなので、機会があればトライしてみたいと思います。については、こちらのページに纏めました

 最後に毎度の繰り返しですが、実用目的であれば、macoslib(当然のごとく、対応している)をそのまま利用させて頂くことを考えた方がいいでしょう。
追記:インスペクターバーの制約について、こちらのページに纏めました

 お世話になったサイト

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

 参考サイト(1):添付書類付きリッチテキスト(.rtfd)の実態 - ザリガニが見ていた...。
 参考サイト(2):24 | 3月 | 2008 | QueueSoft Developing Diary


 更新履歴

 2017.08.15 Xojoでの実装の5項に、「インスペクターバー付加によるHeightの増加に、ウィンドウが追随しないことへの対策」を追加。
 2016.05.01 Xojoでの実装の9項に、注を追加
 2016.04.05 おわりにの記述に、別記事へのリンクを追加
 2016.03.19 おわりにの記述に、追記を追加
 2015.11.03 新規作成


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