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

 Xojo / Real Studio Trial and Error

CocoaのDeclareでリッチテキストを扱う・印刷する

目次
 はじめに

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

 リッチテキストの印刷を試してみました。

 なお検証には、Xojo 2016 Release 3を用いています。(Mac mini mid 2010 + OS X 10.11.6 El Capitan)
注1)印刷機能は、リッチテキストに限定されるものではありませんが、話題の流れ上、ここに置いています。
注2)以下の記事は、手元のプリンタ(キヤノン PIXUS iP4100)で検証したものです。他のプリンタでも同様に動作するかは不明です。

 テキストの印刷

 Cocoaでの印刷の仕方については、以下の公式ガイドに記載されています。

 参考サイト(1):About Printing on the Mac

 今回、印刷の対象とするビューは、(ガイドで言及されているNSViewではなく)NSTextViewです。
NSTextViewは、NSViewと違って、折り返しや改ページが文字を意識して行われるため、印刷境界で分断されて文字が読めなくなることがない。
 単に印刷するだけなら、上記ガイドの例(こちらのListing 3-1。Xojoでの記述は以下の例1で示した。)の通り実装すればよく、簡単です。
 ただしこの方法では、(当方の実験結果では)横方向が用紙幅をオーバーした分は印刷されませんでしたので、全て印刷したい場合はビューの幅をウィンドウ上で適切に調整する必要がありました。
 一方、画面サイズに関係なく、内容を用紙内に収まるように印刷したい場合は、用紙サイズやマージン(余白)といったパラメータをビューのサイズに反映させる必要が生じます。(この辺の考え方は、Xojoネイティブの印刷と基本的に同じと言っていいと思います。)
WYSIWYGを目指すのであれば、画面用と印刷用のビューを同期させるための工夫が必要になると思われるが、そこまではしない。
 パラメータの標準値は、NSPrintInfoクラスのsharedPrintInfoに保持されています。(上記例の印刷でも、暗黙の内にこのパラメータが使用されます。)
 なので、初回はこれをベースに、必要に応じてカスタマイズすることになろうかと思われます。
 次回以降は、カスタマイズした結果を内部で保持することにより、使い回すこともできます。

 パラメータのうち、用紙サイズや方向は、「ページ設定」パネルで指定/取得できます。
 部数やページの選択は、「プリント」パネルで指定/取得できます。(カスタマイズすれば、こちらで用紙サイズや方向を扱うことも可。)
 なお、マージンはプリンタ側からの要請で決まるので、ユーザが指定することはありません。(内部的に変更することは可。)

 ヘッダ/フッタを付加したい場合は、これも上記ガイドのこちらのページにあるように、ビュー内に描画するのではなく、別途、drawPageBorderWithSize:メソッドをオーバーライドして、こちらで対応することになります。メソッドのオーバーライドには、お馴染みのObjective-CのランタイムAPIを用いた動的クラス生成を用います。

 パラメータのファイル保存は、(プリント用パネルで表示される)「プリセット」とは連動せず、独自に保存しています。(アップルのドキュメントによると、通常のplistでは保存できないからNSKeyedArchiver使えとかあるのですが、プリセットがこの辺とどう関わってくるのか、よく分かっていません。)
 また当初は、NSPrintInfoを丸ごと保存/復元しようとしたのですが、うまくいかなかった(注3)ので、dictionaryを取り出して保存、復元時はdictionaryからNSPrintInfoを生成するようにしています。
注3)保存/復元のプロセスは通るが、使おうとすると、そんなメソッドはないとエラーが出る。どうも、復元したものがNSPrintInfo型と認識されていないようなのだが、それ以上は未調査。

 モーダルダイアログとシート

 ページ設定/プリント用パネルには、モーダルダイアログ(以下、ダイアログ)とシートがあります。
 ダイアログとシートには外観の違いだけでなく、動作にも違いがあるようです。

 参考サイト(2):NSApplication(モーダルダイアログの項)
 参考サイト(3):Introduction to Sheets
ダイアログメソッドとシートメソッドそれぞれの直後にmsgBoxを置いてみると、ダイアログメソッドではダイアログを閉じてから表示されるが、シートメソッドではシート表示後直ちに表示される。
 この関係で、シートではボタンのクリックをセレクタで処理するようになっていますが、これもObjective-CのランタイムAPIを用いた動的クラス生成で対応します。
 ただし、実験の結果では、セレクタは特に指定しなくても、動作に支障はないようです。(ボタンごとの動作やPrintInfoへの反映はシステム側がやってくれる。)
以下に示す例2では、保存可否フラグの設定に使っているが、これも常に保存(または常に保存しない)であれば、必要はない。
 また、ダイアログ/シートを出すクラスが複数あります。確認できただけでも、
種別 クラス名 メソッド名
ページ設定 NSApplication  -runPageLayout:(ダイアログ)
NSPageLayout  -runModal(ダイアログ)
 -runModalWithPrintInfo:(ダイアログ)
 -beginSheetWithPrintInfo:modalForWindow:delegate:didEndSelector:contextInfo:(シート)
プリント NSPrintPanel  -runModal(ダイアログ)
 -beginSheetWithPrintInfo:modalForWindow:delegate:didEndSelector:contextInfo:(シート)
NSPrintOperation  -showsPrintPanel(ダイアログ)
 -runOperationModalForWindow:delegate:didRunSelector:contextInfo:(シート)
 それぞれ試してみましたが、NSPrintPanelのメソッドについては、ビューの指定方法が分からなかったため、以降のサンプルではNSPrintOperationのメソッドを使っています。
どうも、プリントパネルのプレビュー画面と「プレビューでPDFを開く」ボタンの機能は、ビューを指定することで有効になるようだ。(推測)

<参考>
 ページ設定用シートでは、セレクタとして指定したメソッド内では、変更後の値を取得できませんでした。どうもパラメータが書き変わるまでに暫く時間がかかるようで、保存したい場合はタイマーで時間差を設けてやる等の必要がありそうです。おそらく、このタイミングでは保存せずに、ウィンドウクローズ時等に保存するのではないかと思われるのですが、詳細は未調査。

 プリント用シートでは、セレクタとして指定したメソッドに渡ってくるリターンコードが、OKボタンとプレビューボタンで同じ値になります(アップルのドキュメントにもあるので仕様のようだ)が、前述の通り、ボタン処理はシステム側がやってくれるので、これで困ることはなさそうです。

 サンプルの仕様

 以上を踏まえて今回は、印刷の実行のみのシンプルなものと、ページ設定に内容をフィットさせてヘッダ/フッタを付加するものを試してみることにしました。
 それぞれの仕様は以下の通りとしました。

 例1(シンプル)
 例2(ページ設定に内容をフィットさせてヘッダ/フッタを付加)

 Xojoでの実装(例1)
  1. 前回プロジェクトをベースとする
  2. MainMenuBarのFileMenuにFilePrintを追加
  3. 以下をWindow1のメニューハンドラ(Menu Handlers)に追加
    メニュー項目名: FilePrint
    
    // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    
    // TextArea1のDocumentViewを取得
    declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
    Dim textView As Ptr = documentView(TextArea1.Handle)
    
    // PrintOperationの生成
    Dim pope As Ptr = NSClassFromString("NSPrintOperation")
    Declare Function printOperation Lib "Cocoa" selector "printOperationWithView:" (class_id As Ptr, view As Ptr) As Ptr
    pope = printOperation(pope, textView)
    
    // PrintOperationの実行(ダイアログ生成を伴う)
    Declare Sub runOperation Lib "Cocoa" selector "runOperation" (obj_id As Ptr)
    runOperation(pope)
    
    Return True
    
 実行してみたところ、印刷が機能することを確認しました。


 Xojoでの実装(例2)
  1. 前回プロジェクトをベースとする
  2. MainMenuBarのFileMenuにFilePagesetupを追加
  3. MainMenuBarのFileMenuにFilePrintを追加
  4. 以下をWindow1のメニューハンドラ(Menu Handlers)に追加
    メニュー項目名: FilePagesetup
    
    PageSetup()
    Return True
    
  5. 以下をWindow1のメニューハンドラ(Menu Handlers)に追加
    メニュー項目名: FilePrint
    
    PrintContent()
    Return True
    
  6. 以下をWindow1のCancelCloseイベントに追加
    // PrintInfoの書き出し
    SavePinfo()
    
  7. 以下をWindow1のOpenイベントに追加(既プロジェクトで記述したコードの後に追加)
    // PrintInfoの読み込み
    OpenPinfo()
    
  8. 以下をWindow1のメソッド(Methods)に追加
    メソッド名: OpenPinfo
    
    // プリンタ情報ファイルの取得
    Dim f as FolderItem = SpecialFolder.Desktop.Child("pinfo")
    
    // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    
    Dim pinfo As Ptr = NSClassFromString("NSPrintInfo")  // クラスメソッドなので、まずNSPrintInfoクラスを取得
    if f.Exists=false then  // ファイルが存在しなければ
        
        // 標準のPrintInfoを取得
        Declare Function sharedPrintInfo Lib "Cocoa" Selector "sharedPrintInfo" (receiver As Ptr) As Ptr
        pinfo = sharedPrintInfo(pinfo)
        
    else
        
        // ファイルからdataを取得。この時、NSDictionaryとして復元される。(NSKeyedUnarchiverを使用)
        Dim keyUarc As Ptr = NSClassFromString("NSKeyedUnarchiver")  // クラスメソッドなので、まずNSKeyedUnarchiverクラスを取得
        Declare Function unarchiveObjectWithFile Lib "Cocoa" Selector "unarchiveObjectWithFile:" (receiver As Ptr, path As CFStringRef) As Ptr
        Dim dict As Ptr = unarchiveObjectWithFile(keyUarc, f.NativePath)  // ファイルから読み込み
        
        // NSDictionaryからNSPrintInfoを生成
        Declare Function alloc Lib "Cocoa" selector "alloc" (receiver As Ptr) As Ptr
        Declare Function initWithDictionary Lib "Cocoa" selector "initWithDictionary:" (receiver As Ptr, path As Ptr) As Ptr
        pinfo = initWithDictionary(alloc(pinfo), dict)
        
    end if
      
    // PrintInfoを保持
    PrintSheet.printInfo = pinfo
    
    注)ファイルの場所は、ここではデスクトップとしましたが、必要に応じて変更して下さい。

  9. 以下をWindow1のメソッド(Methods)に追加
    メソッド名: PageSetup
    
    // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
    
    // NSPageLayoutの取得
    Dim pnt1 As Ptr = NSClassFromString("NSPageLayout")
    Declare Function pageLayout Lib "Cocoa" Selector "pageLayout" (receiver As Ptr) As Ptr
    pnt1 = pageLayout(pnt1)
    
    // ページ設定シートを表示
    Declare Sub beginSheetWithPrintInfo Lib "Cocoa" Selector "beginSheetWithPrintInfo:modalForWindow:delegate:didEndSelector:contextInfo:" (receiver As Ptr, info As Ptr, win As Integer, dlgt As Ptr, sel As Ptr, cntx As Ptr)
    beginSheetWithPrintInfo(pnt1, PrintSheet.printInfo, self.Handle, PrintSheet.makeDelegate(), NSSelectorFromString("pageLayoutDidEnd:returnCode:conextInfo:"), nil)
    
    注)didEndSelectorで指定するセレクタの引数は、こちらに準拠しています。

  10. 以下をWindow1のメソッド(Methods)に追加
    メソッド名: PrintContent
    
    // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
    
    // NSPrintInfoの取得とセット(ポインターの代入だと元のPrintDialog.printInfoが書き換わってしまうので、コピーを作る)
    Declare Function dictionary Lib "Cocoa" selector "dictionary" (class_id As Ptr) As Ptr
    Dim dict As Ptr = dictionary(PrintSheet.printInfo)  // PrintDialog.printInfoからdictionaryを取得
    Dim pinfo As Ptr = NSClassFromString("NSPrintInfo")  // クラスメソッドなので、まずNSPrintInfoクラスを取得
    Declare Function alloc Lib "Cocoa" selector "alloc" (receiver As Ptr) As Ptr
    Declare Function initWithDictionary Lib "Cocoa" selector "initWithDictionary:" (receiver As Ptr, path As Ptr) As Ptr
    pinfo = initWithDictionary(alloc(pinfo), dict)  // dictionaryからNSPrintInfoを生成
    
    // パラメータのカスタマイズ
    Declare Sub setHorizontallyCentered Lib "Cocoa" selector "setHorizontallyCentered:" (obj_id As Ptr, flg As Boolean)
    setHorizontallyCentered(pinfo, false)  // 水平方向のセンタリングを無効にセット
    Declare Sub setVerticallyCentered Lib "Cocoa" selector "setVerticallyCentered:" (obj_id As Ptr, flg As Boolean)
    setVerticallyCentered(pinfo, false)  // 垂直方向のセンタリングを無効にセット
    
    Declare Function paperSize Lib "Cocoa" selector "paperSize" (obj_id As Ptr) As NSSize
    Dim paperSize As NSSize = paperSize(pinfo)
    Dim margin As GFMargin
    Declare Function leftMargin Lib "Cocoa" selector "leftMargin" (obj_id As Ptr) As Single
    margin.Left = leftMargin(pinfo)  // 左マージンの取得
    Declare Function topMargin Lib "Cocoa" selector "topMargin" (obj_id As Ptr) As Single
    margin.Top = topMargin(pinfo)  // 上マージンの取得
    Declare Function rightMargin Lib "Cocoa" selector "rightMargin" (obj_id As Ptr) As Single
    margin.Right = rightMargin(pinfo)  // 右マージンの取得
    Declare Function bottomMargin Lib "Cocoa" selector "bottomMargin" (obj_id As Ptr) As Single
    margin.Bottom = bottomMargin(pinfo)  // 下マージンの取得
    
    // NSTextViewPrintの生成。引数は順に、生成したインスタンスへのポインタ(戻り値)、用紙サイズ、マージン、書類名(ウィンドウのタイトル)
    Dim textView As Ptr
    Dim a As NSTextViewPrint = new NSTextViewPrint(textView,paperSize,margin,me.Title)
    
    // NSTextViewPrintのインスタンスにTextArea1の内容をセット
    PrintContent2(textView)
    
    // Bottomが上辺の設定になっている
    Declare Sub setBottomMargin Lib "Cocoa" selector "setBottomMargin:" (obj_id As Ptr, mgn As Single)
    setBottomMargin(pinfo, margin.Bottom+17)  // ヘッダ領域分を差し引く
    // Topが下辺の設定になっている
    Declare Sub setTopMargin Lib "Cocoa" selector "setTopMargin:" (obj_id As Ptr, mgn As Single)
    setTopMargin(pinfo, margin.Top+15)  // フッタ領域分を差し引く
    
    // PrintOperationの生成
    Dim pope As Ptr = NSClassFromString("NSPrintOperation")
    Declare Function printOperation Lib "Cocoa" selector "printOperationWithView:printInfo:" (class_id As Ptr, view As Ptr, pinfo As Ptr) As Ptr
    pope = printOperation(pope, textView, pinfo)
    
    // PrintOperationの実行(シート生成を伴う。実行をPrintOperation側に任せる場合は、delegate,didRunSelectorは設定不要?っぽい)
    Declare Sub runOperationModalForWindow Lib "Cocoa" Selector "runOperationModalForWindow:delegate:didRunSelector:contextInfo:" (receiver As Ptr, win As Integer, dlgt As Ptr, sel As Ptr, cntx As Ptr)
    runOperationModalForWindow(pope, Window1.Handle, nil, nil, nil)
    
  11. 以下をWindow1のメソッド(Methods)に追加
    メソッド名: PrintContent2
    引数: textView As Ptr
    
    // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    
    // TextArea1のDocumentViewを取得
    declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
    Dim pnt1 As Ptr = documentView(TextArea1.Handle)  // ef.Handle = NSScrollView*
    
    // TextArea1のNSTextStorageを取得(=NSAttributedStringの取得)
    declare function textStorage lib "Cocoa" selector "textStorage" (obj_id as Ptr) As Ptr  // Return NSTextStorage*
    Dim attr As Ptr = textStorage(pnt1)
    
    // textViewのNSTextStorageを取得
    Dim pnt3 As Ptr = textStorage(textView)
    
    // ストレージにスタイル付きテキストをセット
    Declare Sub setAttributedString Lib "Cocoa" Selector "setAttributedString:" (receiver As Ptr, identifier As Ptr)
    setAttributedString(pnt3, attr)
    
  12. 以下をWindow1のメソッド(Methods)に追加
    メソッド名: SavePinfo
      
    // NSPrintInfo保存の操作がされていなければ戻る
    if PrintSheet.saved=false then
        return
    end if
    
    // プリンタ情報ファイルの取得
    Dim f as FolderItem = SpecialFolder.Desktop.Child("pinfo")
    
    // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    
    // NSPrintInfoからdictionaryを取得
    Declare Function dictionary Lib "Cocoa" selector "dictionary" (class_id As Ptr) As Ptr
    Dim dict As Ptr = dictionary(PrintSheet.printInfo)
    
    // dictionaryをNSDataに変換(NSKeyedArchiverを使用)
    Dim keyArc As Ptr = NSClassFromString("NSKeyedArchiver")  // クラスメソッドなので、まずNSKeyedArchiverクラスを取得
    Declare Function archivedDataWithRootObject Lib "Cocoa" Selector "archivedDataWithRootObject:" (receiver As Ptr, obj As Ptr) As Ptr
    Dim data As Ptr = archivedDataWithRootObject(keyArc, dict)  // Return NSData*
    
    // NSDataをファイルに書き出し
    Declare Sub writeToFile Lib "Cocoa" Selector "writeToFile:atomically:" (receiver As Ptr, path As CFStringRef, flag As Boolean)
    writeToFile(data, f.NativePath, true)  // ファイルに保存
    
    注)ファイルの場所は、ここではデスクトップとしましたが、必要に応じて変更して下さい。

  13. 新規クラスを作成(名前は、ここでは「NSTextViewPrint」とした。)
  14. 以下をNSTextViewPrintのメソッド(Methods)に追加(注:Constructorは予約語で、クラスをnewした時に自動的に呼び出される。)
    メソッド名: Constructor
    引数:byRef inst As Ptr, paperSize As NSSize, margin As GFMargin, title As String
    
    // NSTextViewを継承したカスタムクラスを作成。初回のみ
    makeClass()
    
    // 本文エリア(印刷可能領域から、ヘッダ/フッタ領域を差し引いたもの)のRect生成
    Dim rect As NSRect = NSMakeRect(0, 0, paperSize.Width-margin.Left-margin.Right, paperSize.Height-margin.Top-15-margin.Bottom-17)
    
    // インスタンスを作成
    Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
    Declare Function initWithFrame Lib "Cocoa" selector "initWithFrame:" (obj_id As Ptr, frame As NSRect) As Ptr
    inst = initWithFrame(alloc(NSTextViewClass), rect)
    
    // インスタンスを保持
    NSTextViewInst = inst
    XojoInst = self
    
    // 外部指定されたパラメータを保持
    pPaperSize = paperSize
    pMargin = margin
    pTitle = title
    
  15. 以下をNSTextViewPrintのメソッド(Methods)に追加
    メソッド名: DrawPageBorderWithSize
    引数:size As NSSize
    
    // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    
    // ヘッダ(左)用スタイル付きテキスト生成
    Dim stHL As Ptr = GetHeaderString(pTitle, 0)  // 0 = NSLeftTextAlignment
    // ヘッダ(右)用スタイル付きテキスト生成
    Dim d As new date  // 現在日時の取得
    Dim stHR As Ptr = GetHeaderString(d.LongDate+" "+d.LongTime, 1)  // 1 = NSRightTextAlignment
    // ヘッダのポジション
    Dim rectH As NSRect = NSMakeRect(pMargin.Left, pMargin.Top-3, pPaperSize.Width-pMargin.Left-pMargin.Right, 22)
    
    // フッタ用スタイル付きテキスト生成
    Dim stF As Ptr = GetFooterSrring()
    // フッタのポジション
    Dim rectF As NSRect = NSMakeRect(0, pPaperSize.Height-pMargin.Bottom-10, pPaperSize.Width, 22)
    
    // ロックフォーカス(これをしないと、文字が上下反転する)
    Declare Sub lockFocus Lib "Cocoa" selector "lockFocus" (class_id As Ptr)
    lockFocus(NSTextViewInst)
    
    // ヘッダ・フッタの描画
    Declare Sub drawInRect Lib "Cocoa" selector "drawInRect:" (class_id As Ptr, rect As NSRect)
    drawInRect(stHL, rectH)
    drawInRect(stHR, rectH)
    drawInRect(stF, rectF)
    
    // アンロックフォーカス
    Declare Sub unlockFocus Lib "Cocoa" selector "unlockFocus" (class_id As Ptr)
    unlockFocus(NSTextViewInst)
    
  16. 以下をNSTextViewPrintのメソッド(Methods)に追加
    メソッド名: GetFooterSrring
    戻り値型: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 Sub setObject Lib "Cocoa" Selector "setObject:forKey:" (receiver As Ptr, obj As Ptr, key As CFStringRef)
    
    // アトリビュート用Dictionaryの生成
    Dim option As Ptr = NSClassFromString("NSMutableDictionary")
    Declare Function dictionary Lib "Cocoa" Selector "dictionary" (receiver As Ptr) As Ptr
    option = dictionary(option)
    
    // フォントの設定
    Dim font As Ptr = NSClassFromString("NSFont")
    Declare Function systemFontOfSize Lib "Cocoa" Selector "systemFontOfSize:" (receiver As Ptr, size As Single) As Ptr
    font = systemFontOfSize(font, 9.0)
    
    // フォントをDictionaryにセット
    setObject(option, font, "NSFont")
    
    // ParagraphStyleの生成
    Dim pgf As Ptr = NSClassFromString("NSMutableParagraphStyle")
    Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr
    pgf = init(alloc(pgf))
    
    // 揃えの設定(ここではセンタリングを指定)
    Declare Sub setAlignment Lib "Cocoa" Selector "setAlignment:" (receiver As Ptr, type As Integer)
    setAlignment(pgf, 2)  // 2 = NSCenterTextAlignment
    
    // ParagraphStyleをDictionaryにセット
    setObject(option, pgf, "NSParagraphStyle")
    
    // 現在印刷中のページ番号を取得
    Dim pope As Ptr = NSClassFromString("NSPrintOperation")
    Declare Function currentOperation Lib "Cocoa" selector "currentOperation" (class_id As Ptr) As Ptr
    pope = currentOperation(pope)
    Declare Function currentPage Lib "Cocoa" selector "currentPage" (class_id As Ptr) As Integer
    Dim page As Integer = currentPage(pope)
    
    // スタイル付きテキストを生成
    Dim attStr As Ptr = NSClassFromString("NSAttributedString")
    attStr = alloc(attStr)
    Declare Function initWithString Lib "Cocoa" Selector "initWithString:attributes:" (receiver As Ptr, str As CFStringRef, attr As Ptr) As Ptr
    attStr = initWithString(attStr, "- "+Str(page)+" -", option)
    
    // スタイル付きテキストを返す
    return attStr
    
  17. 以下をNSTextViewPrintのメソッド(Methods)に追加
    メソッド名: GetHeaderString
    引数:txt As String, align As Integer
    戻り値型: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 Sub setObject Lib "Cocoa" Selector "setObject:forKey:" (receiver As Ptr, obj As Ptr, key As CFStringRef)
    
    // アトリビュート用Dictionaryの生成
    Dim option As Ptr = NSClassFromString("NSMutableDictionary")
    Declare Function dictionary Lib "Cocoa" Selector "dictionary" (receiver As Ptr) As Ptr
    option = dictionary(option)
    
    // フォントの設定
    Dim font As Ptr = NSClassFromString("NSFont")
    Declare Function systemFontOfSize Lib "Cocoa" Selector "systemFontOfSize:" (receiver As Ptr, size As Single) As Ptr
    font = systemFontOfSize(font, 9.0)
    
    // フォントをDictionaryにセット
    setObject(option, font, "NSFont")
    
    // ParagraphStyleの生成
    Dim pgf As Ptr = NSClassFromString("NSMutableParagraphStyle")
    Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr
    pgf = init(alloc(pgf))
    
    // 揃えの設定(ここでは外部からのパラメータを指定)
    Declare Sub setAlignment Lib "Cocoa" Selector "setAlignment:" (receiver As Ptr, type As Integer)
    setAlignment(pgf, align)
    
    // ParagraphStyleをDictionaryにセット
    setObject(option, pgf, "NSParagraphStyle")
    
    // スタイル付きテキストを生成
    Dim attStr As Ptr = NSClassFromString("NSAttributedString")
    attStr = alloc(attStr)
    Declare Function initWithString Lib "Cocoa" Selector "initWithString:attributes:" (receiver As Ptr, str As CFStringRef, attr As Ptr) As Ptr
    attStr = initWithString(attStr, txt, option)
    
    // スタイル付きテキストを返す
    return attStr
    
  18. 以下をNSTextViewPrintのプロパティ(Properties)に追加
    プロパティ名: pMargin
    データ型: GFMargin
    標準値: なし
    
  19. 以下をNSTextViewPrintのプロパティ(Properties)に追加
    プロパティ名: pPaperSize
    データ型: NSSize
    標準値: なし
    
  20. 以下をNSTextViewPrintのプロパティ(Properties)に追加
    プロパティ名: pTitle
    データ型: String
    標準値: なし
    
  21. 以下をNSTextViewPrintの共有メソッド(Shared Methods)に追加
    メソッド名: makeClass
    
    // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
    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 NSTextViewClass <> nil then
        return
    end if
    
    // クラス名をmyNSTextViewPrint、メタクラス名をNSTextViewにして、生成
    Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSTextView"), "myNSTextViewPrint", 0)
    // ランタイムに登録(参照を可能とするため)
    objc_registerClassPair newClassId
    // Delegateの対象となるメソッドを追加(drawPageBorderWithSize:をXojo側で用意したmyDrawPageBorderWithSizeメソッドで受け取る。)
    if not class_addMethod (newClassId, NSSelectorFromString("drawPageBorderWithSize:"), AddressOf myDrawPageBorderWithSize, "v24@0:8@16") then
        msgBox "error1."
        return
    end if
      
    // クラスを保持
    NSTextViewClass = newClassId
    
  22. 以下をNSTextViewPrintの共有メソッド(Shared Methods)に追加
    メソッド名: myDrawPageBorderWithSize
    引数:id As Ptr, SEL As CString, size As NSSize
    
    // インスタンスメソッドに渡す
    XojoInst.DrawPageBorderWithSize(size)
    
    注)SELの型をPtrとしていたが、CStringの方が相応しいので変更した。(未使用なので変更しなくても実害はなし。)(2018.07.30)
  23. 以下をNSTextViewPrintの共有プロパティ(Shared Properties)に追加
    プロパティ名: NSTextViewClass
    データ型: Ptr
    標準値: なし
    
  24. 以下をNSTextViewPrintの共有プロパティ(Shared Properties)に追加
    プロパティ名: NSTextViewInst
    データ型: Ptr
    標準値: なし
    
  25. 以下をNSTextViewPrintの共有プロパティ(Shared Properties)に追加
    プロパティ名: XojoInst
    データ型: NSTextViewPrint
    標準値: なし
    
  26. 新規クラスを作成(名前は、ここでは「PrintSheet」とした。)
  27. 以下をPrintSheetの共有メソッド(Shared Methods)に追加
    メソッド名: pageLayoutDidEnd
    引数:id As Ptr, SEL As CString, sender As Ptr, rcode As Integer, info As Ptr
    
    // rcode = 0 : Cancel
    // rcode = 1 : OK
    
    if rcode=1 then
        // 保存されたことを示すフラグ・オン(終了時にファイル保存するかどうかの判定用)
        saved=true
    end if
    
    注)SELの型をPtrとしていたが、CStringの方が相応しいので変更した。(未使用なので変更しなくても実害はなし。)(2018.07.30)
  28. 以下をPrintSheetの共有メソッド(Shared Methods)に追加
    メソッド名: makeDelegate
     
    // 既に作成済なら値を返す
    if delegateId <> nil then
        return delegateId
    end if
    
    // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
    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
    
    // クラス名をmyClass1Delegate(名前は任意。少なくとも今回のケースでは参照されない。)、メタクラス名をNSObjectにして、生成
    Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), "myClass1Delegate", 0)
    // ランタイムに登録(参照を可能とするため)
    objc_registerClassPair newClassId
    if not class_addMethod (newClassId, NSSelectorFromString("pageLayoutDidEnd:returnCode:conextInfo:"), AddressOf pageLayoutDidEnd, "@@:@") 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
    delegateId = init(alloc(newClassId))
    
    // インスタンスを返す
    return delegateId
    
  29. 以下をPrintSheetの共有プロパティ(Shared Properties)に追加
    プロパティ名: delegateId
    データ型: Ptr
    標準値: なし
    
  30. 以下をPrintSheetの共有プロパティ(Shared Properties)に追加
    プロパティ名: printInfo
    データ型: Ptr
    標準値: なし
    
  31. 以下をPrintSheetの共有プロパティ(Shared Properties)に追加
    プロパティ名: saved
    データ型: Boolean
    標準値: false
    
  32. 新規モジュールを作成(名前は、ここでは「NSGlobals」とした。)
  33. 以下をNSGlobalsの構造体(Structures)に追加
    構造体名: GFMargin
    スコープ:Global
    
    Left As Single
    Top As Single
    Right As Single
    Bottom As Single
    
  34. 他に、NSMakeRect(メソッド)、NSRect/NSSize(構造体)が必要ですが、それらはmacoslibからコピーさせて頂きました。(上記NSGlobalsにコピーする。)
 実行してみたところ、印刷が機能することを確認しました。
S Shot1


 おわりに

 紙に印刷することはあまりないかもしれませんが、PDF出力が手軽にできるという点は使えるかも。
 また、試しに用紙サイズを超える画像を貼り付けてみましたが、例1/例2とも、縦方向にはみ出した分は改ページされて印刷されましたが、横方向にはみ出した分は印刷されませんでした。テキストエディットは印刷範囲に収まるよう縮小していますので、同等の機能を付加する等した方がいいかもしれません。

 なお、今回も仕組みを理解するために、極力シンプルな書き方を心懸けています。
(例えば、allocした変数の一部は然るべきタイミングでReleaseするべきと思われますが、そのためにはコードを工夫する必要があります。)
 実用目的であれば、macoslibを利用させて頂くことを考えた方がいいでしょう。


 お世話になったサイト

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

 参考サイト(1):About Printing on the Mac
 参考サイト(2):NSApplication(モーダルダイアログの項)
 参考サイト(3):Introduction to Sheets


 更新履歴

 2018.07.30 Xojoでの実装(例2)の22,27項を改訂
 2016.10.01 Xojoでの実装(例2)の、18,19,20,23,24,25項で、NSTextViewPrintとすべきところがPrintSheetとなっていたので修正。
 2016.09.24 新規作成


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