ホームページ開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでリッチテキストを扱う・縦書き時の不具合について

 Xojo / Real Studio Trial and Error

CocoaのDeclareでリッチテキストを扱う・縦書き時の不具合について

目次
 はじめに

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

 縦書き時の不具合について調べてみました。

 なお検証には、Xojo 2017 Release 1.1を用いています。(Mac mini 2018 + macOS 10.14.6 Mojave)


 経緯

 macOS 10.14 Mojave以降では、縦書き時に、行位置がズレる現象が発生します。(注:OS X 10.13 High Sierra以前では問題ない。)
この現象は本プロジェクトのみでなく、アップル純正の「テキストエディット」でも生じるので、システム側の問題のようだ。
 挙動を観察してみると、表示されるのはTextAreaの高さに相当する位置のようでした。
 実際、ウィンドウの高さを変化させると、それに合わせて行位置が左右に移動します。
S Shot1
 ということは、領域(Frame)の幅に高さの値が割り当てられている可能性が考えられるので、TextAreaの幅と高さがどうなっているのか、調べてみることにしました。
 XojoのTextAreaは(多分)CocoaのNSScrollViewを継承していて、その(文字入力に関連する)サブビューの構成は以下のようになっています。
NSScrollView
 |
NSTextView
 |
NSClipView

 それぞれの幅と高さを調べたところ、TextViewのみ、縦書き時は、幅に高さの値がセットされていました。
 TextViewとClipViewのFrameは本来一致する筈のものなので、然るべきタイミングでTextViewのFrameを再設定すれば、解消することができそうです。

 タイミングとして考えられるものは、
 1. 文字入力
 2. クリップボードからのペースト
 3. ドラッグ&ドロップ
 4. リサイズ(今回はウィンドウのリサイズ時が該当)
 5. 既に文字が入っている時の縦書き指定

 1.については、当初XojoのKeyDown,KeyUpイベント辺りでなんとかなるかと思ったのですが、値の変更が起きるのはKeyDown後からKeyUp迄の間のようで、KeyUpで書き換えると、KeyDownで移動してKeyUpで元に戻るを繰り返す、という、ちょっと煩わしい状態に陥ってしまいます。
 結局、NSTextViewのdidChangeTextでなら対処できることを確認しました。

 2.についてはNSTextViewのpaste:、3.については同じくconcludeDragOperation:で対処できることを確認しました。
 didChangeTextconcludeDragOperation:は通常のDelegateでもいいのですが、既にMethod Swizzlingが実装済なので、同じやり方で追加することにします。
 4.についてはウィンドウのResizingイベントで対処できることを確認しました。

 5.については、NSAttributedStringに対する縦書き属性の設定をしないことで、対処できることを確認しました。
 この設定は、編集ではなくプレビュー時の不具合対策用でしたが、前回のプレビュー方式の変更で必要なくなっていましたので、削っても問題ありません。

 以上を踏まえ、(残りの)仕様は以下の通りとしました。

 Xojoでの実装
【ソースコードのコピー&ペーストについて】
・ソースコード(グレー背景部分の全文)をコピーし、指定のオブジェクトにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
・ペーストはオブジェクトに行って下さい。オブジェクト内のEvent Handlers/Methods/Properties等にペーストしても、うまくいかない場合があります。
・それでもペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. 前回プロジェクトをベースとする(64bitビルド)
  2. 以下をWindow1にペースト
    Sub Resizing() Handles Resizing
      // 縦書き時の横方向行位置補正
      ResetTextViewFrame()
    End Sub
    
  3. 以下をWindow1のOpenイベント内に追加(me.Height=me.Heightの直後)
    // TextArea1のTextViewとClipViewを取得して保持
    GetViewPtr()
    
  4. 以下をWindow1にペースト
    Private Sub GetViewPtr()
      Dim i, cnt As Integer
      Dim pnt3, pnt4 As Ptr
      Dim st As String
      
      // NSTextViewの取得
      declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
      Dim pnt1 As Ptr = documentView(TextArea1.Handle)  // TextArea1 = NSScrollView
      
      // サブビュー配列を取得
      Declare Function subviews Lib "Cocoa" selector "subviews" (class_id As Integer) As Ptr
      Dim pnt2 As Ptr = subviews(TextArea1.Handle)
      
      // サブビューの個数を取得
      Declare Function count Lib "Cocoa" selector "count" (class_id As Ptr) As Integer
      cnt = count(pnt2)
      
      Declare Function objectAtIndex Lib "Cocoa" selector "objectAtIndex:" (class_id As Ptr, idx As Integer) As Ptr
      Declare Function myClass Lib "Cocoa" selector "class" (class_id As Ptr) As Ptr
      Declare Function description Lib "Cocoa" selector "description" (class_id As Ptr) As CFStringRef
      
      // サブビュー数だけ回す
      for i=0 to cnt-1
        
        // サブビューを順に取得
        pnt3 = objectAtIndex(pnt2, i)
        
        // サブビューのクラスを取得
        pnt4 = myClass(pnt3)
        
        // クラス名を取得
        st = description(pnt4)
        
        // NSClipViewなら
        if st="NSClipView" then
          
          // TextViewとClipViewを共有プロパティに保持
          textVIew=pnt1
          clipView=pnt3
          
          exit
        end if
        
      next
    End Sub
    
  5. Window1のToggleVtextを削除後、以下をペースト
    Private Sub ToggleVtext()
      // トグル動作
      Dim flg As Integer
      if vFlg=false then
        vFlg=true
        flg=1
      else
        vFlg=false
        flg=0
      end if
      
      // NSTextViewの取得
      declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
      Dim pnt1 As Ptr = documentView(TextArea1.Handle)
      
      // NSTextViewに対する縦書き属性の設定(NSTextLayoutOrientationVertical = 1)
      declare sub setLayoutOrientation lib "Cocoa" selector "setLayoutOrientation:" (receiver as Ptr, flag as Integer)
      setLayoutOrientation(pnt1, flg)
    End Sub
    
  6. 以下をWindow1にペースト
    Protected Shared Sub myConcludeDragOperation(id as Ptr, sel as Ptr, sender  As Ptr)
      // 本来のconcludeDragOperation:を実行
      declare sub myConcludeDragOperation lib "Cocoa" selector "myConcludeDragOperation:" (receiver as Ptr, sender As Ptr)
      myConcludeDragOperation(id, sender)
      
      // 縦書き時の横方向行位置補正
      ResetTextViewFrame()
    End Sub
    
  7. 以下をWindow1にペースト
    Protected Shared Sub myDidChangeText(id as Ptr, sel as Ptr)
      // 本来のdidChangeTextを実行
      declare sub myDidChangeText lib "Cocoa" selector "myDidChangeText" (receiver as Ptr)
      myDidChangeText(id)
      
      // 縦書き時の横方向行位置補正
      ResetTextViewFrame()
    End Sub
    
  8. Window1のmyPasteの最後に以下を追加
    // 縦書き時の横方向行位置補正
    ResetTextViewFrame()
    
  9. Window1のRegisterMethodNSTextViewの最後に以下を追加
    // NSTextViewクラスにmyDidChangeTextメソッドを追加(Xojo側で用意したmyDidChangeTextメソッドで受け取る。)
    if not class_addMethod (TextViewPtr, NSSelectorFromString("myDidChangeText"), AddressOf myDidChangeText, "v24@0:8@16") then
      msgBox "error12."
      return
    end if
    // NSTextViewクラスにmyConcludeDragOperation:メソッドを追加(Xojo側で用意したmyConcludeDragOperationメソッドで受け取る。)
    if not class_addMethod (TextViewPtr, NSSelectorFromString("myConcludeDragOperation:"), AddressOf myConcludeDragOperation, "c24@0:8@16") then
      msgBox "error13."
      return
    end if
    
  10. Window1のsetupHookの最後に以下を追加
    orgMethod = class_getInstanceMethod(TextViewPtr, NSSelectorFromString("didChangeText"))
    myMethod = class_getInstanceMethod(TextViewPtr, NSSelectorFromString("myDidChangeText"))
    method_exchangeImplementations(orgMethod, myMethod)
    
    orgMethod = class_getInstanceMethod(TextViewPtr, NSSelectorFromString("concludeDragOperation:"))
    myMethod = class_getInstanceMethod(TextViewPtr, NSSelectorFromString("myConcludeDragOperation:"))
    method_exchangeImplementations(orgMethod, myMethod)
    
  11. 以下をWindow1にペースト
    Private Shared Sub ResetTextViewFrame()
      // ClipViewのFrameを取得
      Declare Function frame Lib "Cocoa" selector "frame" (class_id As Ptr) As NSRect
      Dim rect As NSRect = frame(clipView)
      
      // ClipViewのFrameをTextViewに適用する
      Declare Sub setFrame Lib "Cocoa" selector "setFrame:" (class_id As Ptr, rect As NSRect)
      setFrame(textView, rect)
    End Sub
    
  12. 以下をWindow1にペースト(できなければ共有プロパティに、名前:clipView、データ型:Ptr、を追加)
    Private Shared Property clipView as Ptr
    
  13. 以下をWindow1にペースト(できなければ共有プロパティに、名前:textVIew、データ型:Ptr、を追加)
    Private Shared Property textVIew as Ptr
    
 実行してみたところ、縦書き時に行位置がズレる現象が解消されることを確認しました。


 おわりに

 対症療法的に対応しているので、まだ漏れがあるかもしれません。今後も何かあれば追加していきたいと思っています。


 お世話になったサイト

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


 更新履歴

 2020.07.13 新規作成


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