Xojoの最近のブログ記事

1年半ぶりくらいなので、結構忘れている。メモ大切。

Apple Developer サイト

  1. キーチェーンアクセス.appを起動 > キーチェーンアクセスメニュー > 証明書アシスタント > 認証局に証明書を要求 (証明書は「ディスクに保存」)
  2. Apple の Developerサイトから「Account」へ進む
  3. 「Certificates, Identifiers & Profiles」(真ん中のアイコン) へ進む
  4. 「Mac App Distribution」 を選択
  5. 1.で作成したファイルをアップロードすると証明書が生成されるので、ダウンロードして保存
  6. 続いてMac Installer Distribution を選択し、5.を繰り返す
  7. ダウンロードした証明書を開いてキーチェーンアクセスに登録する
  8. キーチェーンアクセスの画面で名前を確認しておく(例「3rd Party Mac Developer Installer: Alfasado Inc. (XXXXXXXXXX)」「3rd Party Mac Developer Installer: Alfasado Inc. (XXXXXXXXXX)」) 前者はアプリに、後者はインストールパッケージに付与するもの

証明書の種類を選択

productname.entitlementsファイル(名前はなんでも良い)を用意する。最低限「com.apple.security.app-sandbox」に「true」の指定が必要。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
</dict>
</plist>

ターミナルでcodesignコマンドでアプリに署名を付け、productbuildコマンドでインストールパッケージに署名を付ける。

sudo codesign -f -v -s "3rd Party Mac Developer Application: Alfasado Inc. (XXXXXXXXXX)" --entitlement /path/to/productname.entitlements "/Applications/ProductName.app"
sudo productbuild --component /Applications/ProductName.app --sign "3rd Party Mac Developer Installer: Alfasado Inc. (XXXXXXXXXX)" --product "/Applications/ProductName.app/Contents/info.plist" ProductName.pkg

最後にApplication LoaderでAppleへの登録を行う。

MAUSの改良とあわせてブログテンプレートを久しぶりに微調整してみてる。

og:imageの設定

og:imageをカスタムフィールド(画像)で指定できるようにした。テンプレートはMT側で書く(もうちょっと短く書けそうだけど。

<MTFor regex_replace="/(?:\r?\n|\r)/g","" setvar="entry_og_image">
<MTIf tag="EntryOGImage">
<MTEntryOGImageAsset>
<$MTAssetProperty property="image_width" setvar="og_width"$>
<$MTAssetProperty property="image_height" setvar="og_height"$>
<MTIf name="og_width" eq="$og_height">
<$MTAssetUrl encode_html="1"$>
<MTElse>
<$mt:AssetThumbnailURL height="600" square="1" encode_html="1"$>
</MTIf>
<MTElse>
<mt:EntryAssets type="image" limit="1">
<$MTAssetProperty property="image_width" setvar="og_width"$>
<$MTAssetProperty property="image_height" setvar="og_height"$>
<MTIf name="og_width" eq="$og_height">
<$MTAssetUrl encode_html="1"$>
<MTElse>
<$mt:AssetThumbnailURL width="600" square="1" encode_html="1"$>
</MTIf>
<mt:Else>
</mt:EntryAssets>
</MTEntryOGImageAsset>
</MTIf>
</MTFor>
<mt:unless name="entry_og_image">
<mt:Assets type="image" tag="@site_icon" limit="1" regex_replace="/(?:\r?\n|\r)/g","" setvar="entry_og_image">
<$MTAssetProperty property="image_width" setvar="og_width"$>
<$MTAssetProperty property="image_height" setvar="og_height"$>
<MTIf name="og_width" eq="$og_height">
<$MTAssetUrl encode_html="1"$>
<MTElse>
<$mt:AssetThumbnailURL height="600" square="1" encode_html="1"$>
</MTIf>
</mt:Assets>
</mt:unless>

<!-- Open Graph Protocol -->
<meta property="og:type" content="article">
<meta property="og:locale" content="<$mt:BlogLanguage setvar="blog_lang"$><mt:If name="blog_lang" eq="ja">ja_JP<mt:else><$mt:Var name="blog_lang"$></mt:If>">
<meta property="og:title" content="<$mt:EntryTitle encode_html="1"$>">
<meta property="og:url" content="<$mt:EntryPermalink encode_html="1"$>">
<meta property="og:description" content="<$mt:EntryExcerpt remove_html="1" encode_html="1"$>">
<meta property="og:site_name" content="<$mt:BlogName encode_html="1"$>">
<mt:if name="entry_og_image"><meta property="og:image" content="<mt:var name="entry_og_image">"></mt:if>

画像挿入ダイアログ

リンク挿入ダイアログ

MarkdawnではなくHTMLを貼付けられるようにしたり、URLからページタイトル取得できるようにしたり。

Sub Action(itemIndex as integer)
  If URLField.Text <> "" Then
    Dim Timeout As Integer = 40
    Dim MySocket As New HTTPSocket
    GetProgress.Visible = True
    GetProgress.Refresh
    Dim Response As String = MySocket.Get( URLField.Text, Timeout )
    Dim StatusCode As Integer = MySocket.HTTPStatusCode
    If StatusCode = 200 Then
        Response = DefineEncoding( Response, Encodings.UTF8 )
        Dim Title As String
        Title = NthField( Response, "</title>", 1 )
        Title = NthField( Title, "<title", 2 )
        Title = NthField( Title, ">", 2 )
        Title = ReplaceAll( Title, Chr( 10 ), "" )
        Title = ReplaceAll( Title, Chr( 13 ), "" )
        LabelField.Text = Title
        GetProgress.Visible = False
        Return
      End If
    End If
  End If
  Beep
  GetProgress.Visible = False
End Sub

(3月27日追記)Timeout40秒はちょっと長いのと、文字コードShift_JISとかEUC-JPとかあるので、その辺を修正...

Dim Timeout As Integer = 20
Dim MySocket As New HTTPSocket
Dim Response As String = MySocket.Get( URLField.Text, Timeout )
Headers = APISocket.PageHeaders
Dim HeaderSource As String = Headers.Source
Dim HeaderArr() As String
HeaderArr = SplitB( HeaderSource, Chr( 13 ) + Chr( 10 ) )
Dim StatusCode As Integer = MySocket.HTTPStatusCode
If StatusCode = 200 Then
Dim Charset As String
If UBound( HeaderArr ) > -1 Then
  For i As Integer = 0 To UBound( HeaderArr )
    Dim Hd As String = HeaderArr( i )
    If InStr( Hd, "charset=" ) <> 0 Then
      Charset = NthField( Hd, "charset=", 2 )
    End If
  Next
End If
If Charset = "" Then
  Charset = NthField( Response, "charset=", 2 )
  If Charset <> "" Then
    Charset = NthField( Charset, Chr( 34 ), 1 )
  End If
End If
Dim TextEnc As TextEncoding
If Charset <> "UTF-8" Then
  TextEnc = GetInternetTextEncoding( Charset )
  Response = DefineEncoding( Response, TextEnc )
  Response = Response.ConvertEncoding( Encodings.UTF8 )
Else
  Response = DefineEncoding( Response, Encodings.UTF8 )
End If

ブログエディタに印刷機能は要らんと思うのですが、まぁ、エディタだからね。一応。

印刷対応

印刷設定でサイズを%指定した場合のプリントのサンプル。

動的に生成したTextAreaのStyledTextPrinterをコールするとNilObjectionのエラーになるので、ダミーのテキストエリアを配置しておいて隠しておく。TextAreaは両方ともStyledはOff。動的に生成したコントロールがNilになるところは納得いってない。バグじゃねーのとか思うけど、とにかくこれで動くのであるよ。

ダミーのテキストエリア

macoslib が便利すぎる

| コメント(0) | トラックバック(0)

Xojoがマルチプラットフォームなアプリを作れるとは言っても、作り込みを進めて行くとやっぱり特定のOS向けのソフトウェアになっていってしまう今日この頃。どうせ特定のOS特化していくなら、やっぱりOSネイティブの機能を使いたくなりますよね。昔はこんな本があったんですけどね...今や本家マニュアルの日本語訳すらありません。で、結局有償のプラグインということになるのですが(おや、お金がかかることは構わないのですけども←自分もそういう商売やってんじゃん)、macoslibというプロジェクトがある。これが、本当素敵すぎる。

macoslibは、要するに、XojoからOS Xへアクセスするための様々なライブラリを提供してくれます。尚、紹介するのはごくごく一部の機能です。興味のある方はご自身でお試しください。

ボタンにシステムアイコンをセット

実は... 一番時間がかかってるのがアイコン作成だったり(プログラマあるある的)。macoslibからOSアイコンの取得ができるので、使わない手はないと。

SegmentedControl のOpenイベントに

me.Items( 0 ).Icon = SystemIcons.QuickLookTemplate

で、システムアイコン(クイックルック)を取得してセットしてくれます。Retinaディスプレイの場合、大きなサイズのアイコンが戻ってきてしまうので(あと、色を変更したいというあたり)、実際は以下のような感じで、いい感じに。

クイックルックのアイコンをボタンアイコンにセット

me.Items( 0 ).Icon = GetIcon( SystemIcons.QuickLookTemplate.IconTemplateSetColor( &c333333 ) )

Sub GetIcon( P As Picture ) As Picture
  Dim SP As Picture
  If ScalingFactor > 1 Then
    SP = New Picture( P.Width / 2,  P.Height / 2, 32 )
    SP.Graphics.DrawPicture( P, 0, 0, P.Width / 2, P.Height / 2 , 0, 0, P.Width, P.Height  )
    SP.Transparent = 1
  Else
    SP = P
  End If
  Return SP
End Sub

NSSharingServicePickerを呼ぶと、シェアメニューを呼び出せる

Facebook自動投稿のMTプラグインとか、僕も作ってるけど(他の方が作成しているTwitterポストプラグインとかも)、殆どはData APIからのPostには対応していません。もちろんプラグインの方を変更してもいいんだけど、macoslibからシェア機能を呼び出せる。

シェアボタン

    Dim picker as New NSSharingServicePicker( New NSURL( Permalink ) )
    picker.ShowRelativeToRect( me.bounds, me.View, Cocoa.NSRectEdge.NSMinYEdge )

スムーズなウィンドウのリサイズ

NSWindowプロパティをウィンドウに設定( m_NSWindow As NSWindow )してOpenイベントで自分自身をセットしておき、

m_NSWindow = self

サイズを変更したいタイミング(MAUSでは、設定パネルのパネルをクリックした時)で、

m_NSWindow.SmoothResize( w, h )

とすると、スムーズなアニメーションでリサイズしてくれます。

検索フィールドを表示する

Canvasを配置して、Superを Cocoa.NSSearchField に設定する。

MAUSScreenSnapz019

Actionイベントで、me.StringValue とすることで値を取得できる。また、Openイベントで

  me.TabStop = true
  me.PlaceholderText = App.kLabelSearch

のようにして、タブフォーカス、CueTextのセットができます。

「このアプリケーションで開く」の実装

MAUSではないのですが、AzureのBlobクライアントを作っていて、直接Blobを編集するところを実装したかったのです。

このアプリケーションで開く

FolderItemをCFURLに変換して、FindAppsForURLを呼ぶと、アプリケーションのリストが返ります。

  Dim Apps() As FolderItem
  Dim CF As CFURL
  if F.Exists Then
    CF = new CFURL( F )
    Apps = FindAppsForURL( CF )
  End If

アプリケーションのアイコンを取得

上の画像の、コンテクストメニューのアイコンをセットしているところ。

      Dim Icon As MacIcon
      Icon = MacIcon.NewIconFromFolderItem( F )
      Dim p As Picture
      Icon.Size = 16
      p = New Picture( 16, 16, 32 )
      Icon.Draw p.Graphics, 0, 0
      Dim Mstr As String = App.kMenuOpenWithDefault
      mItem = New MenuItem( Mstr )
      mItem.Icon = p

冒頭にも書きましたけど、紹介したのはごくごく一部の機能です。興味のある方は是非ご自身でお試しください。

世界でこれが必要な人の数はいったい何人なのだろうw

MTのData APIを Xojo から利用するためのクラスを作った。Mac/Win用のMTクライアントが簡単に作れる筈。今朝書いたのと違ってCURLを使わず、Xojoのネイティブなコードで書いたので、これはWindowsでも動くはず。

Synopsis

Dim DataAPI As MTDataAPI
DataAPI = New MTDataAPI
DataAPI.UserName = "Melody"
DataAPI.Password = "Nelson"
DataAPI.CGIPath = "http://localhost/cgi-bin/mt/"

Properties

  • Username(String : デフォルト値: Melody)
  • Password(String : デフォルト値: Nelson)
  • CGIPath(String : デフォルト値: http://localhost/cgi-bin/mt/)
  • DataAPIScript(String : デフォルト値: mt-data-api.cgi)
  • AdminScript(String : デフォルト値: mt.cgi)
  • APIVersion (String : デフォルト値: v1)
  • AuthRemember(Boolean : デフォルト値 : True)
  • AuthTimeout(Integer : デフォルト値 : 30)
  • AuthTimeout(Integer : デフォルト値 : 30)
  • ClientID(String : デフォルト値: Xojo)
  • GetTimeout(Integer : デフォルト値 : 90)
  • PostTimeout(Integer : デフォルト値 : 60)
  • AccessToken(String : デフォルト値: なし)
  • SessionID(String : デフォルト値: なし)
  • TokenExpires(String : デフォルト値: -1) ※取得したAccessTokenが有効なUnixTimestamp
  • LastErrorCode(JSONItem : デフォルト値: なし)

Method

今のところGetEntries, GetEntry, NewEntry, UpdateEntry, UploadAsset をサポート。

GetEntries(ブログ記事一覧の取得)

Dim JSON As JSONItem
 = DataAPI.GetEntries( <BlogID(Integer)>, <Params(String)>, <Authentication(Boolean)> )


例えば、BlogID が1のエントリーを500件、カラム名を指定し、認証付きでGETする場合

Dim JSON As JSONItem
= DataAPI.GetEntries( 1, "limit=500&fields=title,updatable,id,status,date,body,permalink", True )

GetEntry(単一ブログ記事の取得)

Dim JSON As JSONItem
 = DataAPI.GetEntry( <BlogID(Integer)>, <EntryID(Integer)>, <Authentication(Boolean)> )

NewEntry(ブログ記事の投稿)

Dim JSON As JSONItem
 = DataAPI.NewEntry( <BlogID(Integer)>, <Entry(Dictionary)> )

ここでのEntryはDictionaryとして指定する。

Dim BlogId As Integer = 1
Dim Entry As New Dictionary
Entry.Value( "title" ) = "記事のタイトル"
Entry.Value( "body" ) = "記事の本文"
Entry.Value( "status" ) = "Publish"
Dim JSON As JSONItem = DataAPI.NewEntry( BlogId, Entry )
MsgBox( JSON.ToString )

UpdateEntry(ブログ記事の更新)

Dim JSON As JSONItem
 = DataAPI.UpdateEntry( <BlogID(Integer)>, <EntryID(Integer)>, <Entry(Dictionary)> )

UploadAsset(アイテムのアップロード)

Dim JSON As JSONItem
 = DataAPI.UploadAsset( <BlogID(Integer)>, <File(FolderItem)>,
    <Path(String)>, <autoRenameIfExists(Boolean)>, <normalizeOrientation(Boolean)> )

ファイル選択してアップロードするコードは以下の通り。

Dim DataAPI As MTDataAPI
DataAPI = New MTDataAPI
DataAPI.UserName = "Melody"
DataAPI.Password = "Nelson"
DataAPI.CGIPath = "http://localhost/cgi-bin/mt/"
Dim F As FolderItem = GetOpenFolderItem("")
If F <> Nil Then
    Dim JSON As JSONItem = DataAPI.UploadAsset( 1, F, "images", True, True )
    MsgBox( JSON.ToString )
End If

ね、とっても簡単。

XojoからCURL経由でMovable TypeのData APIを叩く時のポイントなど。

JSONにセットする前に特定文字列はUnicodeエスケープシーケンスに変換する

sub RawDataEscape( Param As String )
  Param = ReplaceAll( Param, "\", "\\" )
  Param = ReplaceAll( Param, Chr( 34 ), "\u0022" )
  Param = ReplaceAll( Param, Chr( 10 ), "\u000a" )
  Param = ReplaceAll( Param, "<", "\u003C" )
  Param = ReplaceAll( Param, ">", "\u003E" )
  Param = ReplaceAll( Param, "&", "\u0026" )
  Param = ReplaceAll( Param, "'", "\u0027" )
  Param = ReplaceAll( Param, ";", "\u003B" )
  Return Param
end sub

コマンドに渡す直前にJSON文字列のダブルコーテーションをエスケープする

sub JSONEscape( Param As String )
  Param = ReplaceAll( Param, Chr( 34 ), "\" + Chr( 34 ) )
  Return Param
end sub

Shellに渡す文字列をエスケープする

sub ShellEscape( Param As String )
  Param = ReplaceAll( Param, " ", "\ " )
  Param = ReplaceAll( Param, ";", "\;" )
  Return Param
end sub

CURLコマンドには -s オプションを付ける

付けなくてもいいけども、JSONがそのまま受け取れるので。

sub BlogPost( Title,Body,EditEntryId As String )
  Dim j as new JSONItem
  j.Value( "title" ) = App.RawDataEscape( Title )
  j.Value( "body" ) = App.RawDataEscape( Body )
  j.Value( "status" ) = "Publish"
  Dim Entry,Cmd As String
  Entry = App.JSONEscape( j.ToString )
  Cmd = "curl -s -H " + Chr( 34 ) + "X-MT-Authorization: MTAuth accessToken=" + App.AccessToken
  Cmd = Cmd + Chr( 34 ) + " -d entry=" + Chr( 34 ) + Entry + Chr( 34 ) + " "
  If EditEntryId <> "0" Then
    Cmd = Cmd + " -d __method=" + Chr( 34 ) + "PUT" + Chr( 34 ) + " "
  End If
  Cmd = Cmd + APIField.Text + "/" + VersionField.Text + "/sites/" + BlogIDField.Text + "/entries"
  If EditEntryId <> "0" Then
    Cmd = Cmd + "/" + EditEntryId
  End If
  Dim Sh As New Shell
  Sh.Execute( Cmd )
  Dim Result As String 
  Result= Sh.ReadAll
  //...
end sub

複数のパラメタをGETで渡す場合は & を \ でescapeする

sub GetEntries()
  Dim Cmd As String
  Cmd = "curl -s -H " + Chr( 34 ) + "X-MT-Authorization: MTAuth accessToken=" + App.AccessToken
  Cmd = Cmd + Chr( 34 ) + " "
  Cmd = Cmd + APIField.Text + "/" + VersionField.Text + "/sites/" + BlogIDField.Text + "/entries?limit=" + LimitField.Text
  Cmd = Cmd + "\&fields=title,updatable,id,status,date,body,permalink"
  Dim Sh As new Shell
  Sh.Execute( Cmd )
  //...
  Dim Result As String 
  Result= Sh.ReadAll
end sub

MTのData APIを使ってOS Xのアプリケーションを作ってみました。外部エディタを指定することで、リスト画面で項目を右クリックからエントリーの編集や新規投稿ができます。

外部エディタの設定は「External Editor」タブクリックして行います。Markdownを使う場合は、「Format」チェックボックスのMarkdownにチェックを入れて設定を保存してください。このムービーでは、Mou を外部エディタに指定しています。


あとは、エディタ側からドキュメントを保存すれば、投稿や更新をしてくれます。エントリーの保存、削除、ステータスの変更なども行えます。外部エディタなしでも投稿や編集が可能です。その場合は、項目をダブルクリックするか、「Editor」タブクリックで投稿画面に遷移してください。

このアプリケーションは、無保証、β版です。CURLを使っています(これ、入ってるんだよね? デフォルトのMac OS Xに...)。

ダウンロード

Mac OS 10.7以上で動作します。繰り返しますが、このアプリケーションは、無保証、β版です。何が起きようとも作者は一切の責任を負いません。まぁ、見た目も荒削りだし、Data APIのサンプルアプリ的な位置づけです。そのうちちゃんと作るかも。

25日に東京でXojo勉強会を行います(というか、会場提供して1コマお話させていただきます)。会場はさほど広くないのであまり人数に余裕はありませんが、今のところ古参? のユーザーさんが中心のようなので(以前はREALBasicあるいはREAL Studio)、これから、という方にも参加いただけたらいいなと思い、チュートリアル的なものを書いてみます。Xojoのサンプルにもあり、紹介動画もアップされていますが、簡易Webブラウザを作るというものです。


少し古いもの(REAL Studio時代)の動画もあります。

アカウントの作成とダウンロード

以下のページから、アカウントを作成してログイン、まずはダウンロードしてください。

ディレクトリごとアプリケーションフォルダにドロップしてコピーし、起動します(起動したら一旦終了してMacを一度再起動しておいたほうがいいかもしれません)。

Xojo自体は、無償で利用できます。お金がかかるのは作成したアプリをビルドする時。つまり、ダブルクリックで起動する単独のアプリケーションを作成したり配布したりする時にライセンス登録が必要になります。ライセンスはビルドの対象毎に必要です。今回のようなデスクトップアプリのライセンスは 34,000円。他にWebアプリやコンソールアプリ等を作成する場合には別のライセンスが必要です。すべてが含まれるProライセンスというものもあります。

3分で作る簡易ブラウザ

今回作るのは、以下のようなアプリケーションです。

  • テキストフィールドに入力したURLに「Go」ボタンクリックでアクセスすると、ページを表示するWebブラウザ

(ソースコード)ExampleBrowser.zip

ダブルクリックしてXojoを起動したら、「デスクトップアプリケーション」タブを選択し、適当なアプリ名を入力して「OK」をクリックします。

起動時の画面

ウィンドウへのコントロールの配置

デフォルトでウィンドウが1つ登録されています。今回はこれをこのまま使います。 右上の「ライブラリ」ボタンをクリックして(最初はデフォルトで表示されているはずです)各コントロールを表示します。HTMLビューワをウィンドウにドロップして配置します。

HTMLViewerの配置

配置後、マウスドラッグして大きさを調整します。「インスペクタ」ボタンをクリックすると、オブジェクトのプロパティが編集できます。今回は特に名前も設定も変更せず、設定するのは右下の「Locking」の部分のみ。下と右の鍵マークをクリックします。これでウィンドウリサイズ時にHTMLビューワが追随するようになります。

コントロールの配置(四隅をロックする)

同じ要領で、PushボタンとTextフィールドを配置します(冒頭のムービーでは「戻る」「進む」「再読み込み」ボタンを追加していますが、ここでは省略します。Textフィールドの「Locking」は新たに右の鍵を選択します。Pushボタンの「Locking」では、左鍵を外し、逆に右鍵を選択、ボタンのラベル(Caption)に「Go」を入力します。

コーディング

配置を大きさを確定したら、いよいよコードを書きます。といっても、コードはたったの一行ですが。

GOボタンへのコードを追加(1)

Goボタンをダブルクリックします。「イベントハンドラの追加 PushButton 1」と表示され、イベントの一覧が表示されます。「Action」を選択して(最初から選択されているかと思います)、「OK」。

GOボタンへのコードの追加(2)

以下のコードを入力します。

HTMLViewer1.LoadURL( TextField1.Text )

入力途中には入力候補が表示されます。tabで候補を出し、returnで確定です。Xojoでは、大文字と小文字を区別しません(が、気持ち悪いのですよね。正確にタイプしていただいて構いません...)。

実行する

コードの入力と実行

入力できたら、緑のボタン「実行」をクリックします。URLを入力して「Go」ボタンをクリックしてください。ページが表示されましたか? 表示された方は、勉強会参加いただいて大丈夫ですw。尚、実行されるアプリケーションは一時的に作成されるもので、アプリケーションの実行が終了すると自動的に削除されます。単独で起動するアプリケーションを作成する段階になったら、ライセンスの購入を検討すれば良いでしょう。

実行中のブラウザ画面

以上、いかがでしたか? この例はとても簡単だと思いますが、Xojo自体は高度なオブジェクト指向、プラグインによる拡張、OSネイティブの機能へのアクセスなど、本格的な開発が可能なポテンシャルを持っています。Xcodeに挫折した人とかにはお勧め。

Windows版のビルド

画面を見ていただきわかる通り、このキャプチャはMac OS X上のものですが、ビルドライセンスがあればこのまま Windows用のアプリをビルドすることができます。

ビルド設定

Windows 8上で実行してみます。

Windows 8での実行

ちゃんと動いてますね。ということで、興味もたれた方いらっしゃいましたら、是非勉強会へ。

Retina Displayのサポートについては、そのものズバリのブログエントリがあり、最新版にはサンプルディレクトリにサンプルもある(Example Projects/Platform-Specific/OS X/RetinaDisplay/RetinaDisplay.Xojo_binary_project)。こいつを動かせばそのまま動くのだけども、新規に作成したプロジェクトにソースをコピペしてもなぜか動かなかったので、共有しておく。

Retinaディスプレイへの対応方法

(元エントリにあるように)要するに、Info.plistに以下を追加して、Declare Function BackingScaleFactor をコールする。

<key>NSHighResolutionCapable</key>
<true/>

引数はWindowPtrなので、このコードはWindowのメソッドとしておく(もしくは呼び出す際にWindowを指定してやる必要がある)。要はマルチスクリーンのケースがあるので、Windowから判定するということなのだろう。

  #If TargetCocoa Then
    Try
      Soft Declare Function BackingScaleFactor Lib "AppKit" Selector "backingScaleFactor" (target As WindowPtr) As Double
      Return BackingScaleFactor(Self)
    Catch e As ObjCException
      Return 1
    End Try
  #Else
    Return 1
  #Endif

問題はこれをビルド時に追加するためのコードの場所で、これは、「挿入」→「Build Step」→「新しいIDEスクリプト実行手順」を作成して、そこに書くことになる。

  Dim appName As String = CurrentBuildAppName
  appName = ReplaceAll(appName, " ", "\ ") // Escape spaces for the command line
  
  Dim appPath As String = CurrentBuildLocation + "/" + appName + ".app"
  
  Dim command As String
  command = "/usr/bin/defaults write " + appPath + "/Contents/Info ""NSHighResolutionCapable"" YES"
  Call DoShellCommand(command)

Info.plistにXMLを直接書くのではなく、defaultsコマンドを使ってシェル経由で設定を追加している(この仕組みを使うと先に紹介したPerlスクリプトにUIを被せるような用途の際にPerlスクリプトやモジュールを自動的にパッケージに含められて便利)。

ところが、単にこれを書いただけでは正しく実行されない。ビルド設定のOS Xの「Build」の下にDragして配置することではじめて正しく動作します(こういうのがね、日本語マニュアルが無いってのが無駄な労力につながるってんだ)。

XojoのプロジェクトをRetinaDisplay対応させる

Mavericks(OS X10.9)の判別と、ついでにRetinaディスプレイの判別も初期化時に行う方法

ついでに、OS X10.9(Mavericks)の判別が必要になったので、これを行う方法と、マルチスクリーンを考慮しなければ初期化時にRetinaディスプレイ判別もできちゃわないだろうか、ということで、以下(どちらかといえばこっちが本題)。問答無用で、コードをそのまま貼っておく。AppのOpenイベントに書く。

OSのバージョン判定は素直に sw_vers -productVersion をシェルで叩く。その後、screencaptureコマンドを叩くのだけど、Retinaディスプレイだと生成されたキャプチャのピクセルが縦横ともに常に2倍になる。Mavericksかどうかでコマンドのパラメタが違うのは、Mavericks以前では screencapture の-R(ピクセル指定)が有効でないためです。

  #If TargetMacOS Then
    Dim IsMavericks, IsRetina As Boolean
    Dim sh As New Shell
    Dim GetOSVer As String = "sw_vers -productVersion"
    sh.Execute(GetOSVer)
    Dim Res As String = sh.ReadAll
    Dim vNum As Double = Val(Res)
    If vNum >= 10.9 Then
      IsMavericks = True
    End If
    Dim Temp,TestFile As FolderItem
    Temp = SpecialFolder.Temporary.Child( "com.alfasado.test.work" )
    if Temp.Exists = False Then
      Temp.CreateAsFolder
    End If
    Dim OrigWidth As Integer
    Dim CreateCapture As String
    If IsMavericks = True Then
      OrigWidth = 1
      CreateCapture = "cd " + Temp.ShellPath + "; screencapture -R 1,1,1,1" 
    Else
      OrigWidth = Screen(0).Width
      CreateCapture = "cd " + Temp.ShellPath + "; screencapture -x 1,1,1,1" 
    End If
    sh.Execute(CreateCapture)
    TestFile = Temp.Child( "1,1,1,1" )
    If TestFile.Exists Then
      Dim P As Picture
      P = Picture.Open(TestFile)
      If OrigWidth = P.Width Then
        IsRetina = False
      ElseIf OrigWidth * 2 = P.Width Then
        IsRetina = True
      End If
      TestFile.Delete
    End If
    Temp.Delete
    If IsRetina Then
      MsgBox "Retina"
    Else
      MsgBox "Not Retina"
    End If
    If IsMavericks Then
      MsgBox "Mavericks"
    Else
      MsgBox "Not Mavericks"
    End If
  #Endif

以前に本格的にREALBasic使ってたのってOS9の時代だったので。OS Xにすっかり変わっていることでシェルが活用できることが結構大きいな、と。何でもかんでもDeclareってのがパターンだったけど、Tarminal遣いの人にも色々と活用できる。こういう点ではUnix万歳、だね(screencaptureコマンドの仕様が将来変わるかもってのはあるけど、て、まぁあるだろな。互換性だけちゃんとしてくれれば動かなくなることはなかろう)。

関連

screencaptureコマンド


screencapture foo.png

ファイル名を引数に指定すると、そのファイル名でスクリーンキャプチャがカレントディレクトリに生成される

-t pdf

のようにフォーマット指定が可能。省略の場合 png

-x

サウンドを再生しない。"パシャ!" ってやつです。


screencapture -R 117,226,100,100

ピクセル範囲指定<x,y,w,h>。

ファイル名 117,226,100,100 のようなファイルがカレントディレクトリに生成される(ファイル名指定との併用はできない模様)。Retinaディスプレイの場合、この指定では実際は200ピクセル四方となる模様。

sipsコマンド

画像の情報取得、リサイズ、加工など。

sips --resampleWidth 400 from.png --out to.png
幅400pxに拡大(リサイズ)
sips --resampleHeightWidthMax 400 from.png --out to.png

縦横比を変更せず、最大ピクセルを400pxとして拡大(リサイズ)

sips --resampleHeightWidth 400 400 from.png --out to.png

縦横を指定して拡大(変形)

sips -s format jpeg  from.png --out to.jpg

フォーマット変換。品質指定も可(例:-s formatOptions low)

mdfindコマンド

スポットライトのコマンドライン版。高速にファイル検索が可能。

mdfind foo

fooを含むファイルの検索。

mdfind -name foo

ファイル名に「foo」を含むファイルの検索。

mdfind -onlyin /Applications/ 'kMDItemContentTypeTree=="com.apple.application"

アプリケーションディレクトリ以下の種類が「アプリケーション」のファイルをリストアップ。

textutilコマンド

テキスト等のファイルフォーマットの変換など。

textutil -convert html foo.docx -output foo.html

foo.docx(Wordで作成したOpenXML形式のファイル)をhtmlファイルにコンバートする。

qlmanageコマンド

クイックルックのコマンドライン版(本来はデバッグ用ツール)

qlmanage -p foo.png

ファイル名を引数指定するとクイックルックを表示する。本来はXcode等での開発時のデバッグ用ツールのようで、ウィンドウタイトルには[DEBUG]が追加され、実行結果にはデバッグ結果が表示される。

1秒毎に進行して10段階(秒)で終了する処理の進捗をプログレスバーでユーザーにフィードバックする時の話しです。 0から10段階に設定して、1秒毎に1(10%)ずつ進めて行き、10に到達した瞬間に非表示にする(処理終了)という実装をすると、何か違和感がないですか? (以下の動画)

プログラマ的には素直な処理の実装なのですが、以下の2点が違和感につながっているように思います。

  • スタート時、バーが0の時、処理が止まっているように見える。
  • 終了時、9段階から10段階に到達したとき、10段階に到達した画面が見えずに(一瞬で非表示になるため)、違和感が残る。

そこで、以下のように改良します (以下の動画)。

  • 敢えて10段階とせずに、9段階とする。
  • 0段階の時と9段階の時に散髪屋状態(barbar pole)とする
  • 0段階はあえて見せずに、1段階から見せる

最後の10段階をもうちょっとちゃんと見せたほうが完了した感が出るとか、改良点はあると思いますが、GUIをプログラミングする時に、単に処理の都合で安易に実装してしまわずに、例えば処理開始時にバーを1段階進め、最後の処理をbarbar poleにする、そのために敢えて10段階を9段階とする、などの工夫がUXを良くしていくのだろうな、等と考えた年の瀬でした。

おそらく今年最後のブログ記事になるかと思います。みなさま良いお年をお迎えください。そして、来年も何卒宜しくお願いします。

追記

進捗もへったくれもなく、かかる時間を予測してとにかく一定スピードで進めちゃう、みたいな考え方もあるでしょうね。もしくは済んでない(非同期で引き続き実行中な)のにちょっと前には終ったように見せるとか。ウェブアプリとか。失敗したら後で知らせたらいいや、みたいな。

進捗もへったくれも、というあたり、それって正しいプログラミングではないやろ、という意見もあるだろうけど、こっちのほうが体験的に快適という考え方もあるのだろうね。

この記事は Windows Azure Advent Calendar 2013 の17日目の記事です。

AZBlobClient(Mac OS 用Windows Azure Blobクライアント)の画面

当初「PerlモジュールNet::Azure::StorageClientについて」の記事を書こうと思っていたのですが、気が変わったというか、こっちのほうがトピックスとしてはおもしろいと思うので、すいませんが勝手に変えますm(_ _)m

ちなみに、Perlモジュール Net::Azure::StorageClientはこちら。 GItHubのリポジトリのほうが少し更新は早いかもしれません。

それでも? 一応ふれておきますね。

Perlモジュール Net::Azure::StorageClientについて


use Net::Azure::StorageClient::Blob;
my $blobService = Net::Azure::StorageClient::Blob->new(
                                    account_name => $you_account_name,
                                    primary_access_key => $your_primary_access_key,
                                    container_name => $container_name, );
my $path = 'path/to/blob';
my $res = $blobService->get_blob( $path );
  

返り値は HTTP::Response object(s)です。container_nameは省略可能、その場合、コンテナのパスからblobパスを結合して、container_name/path/to/blob のようにして渡せばあとはよしなにしてくれます。

こちらのページのドキュメントを見ていただければわかると思いますが、Blobについては基本的に一通りのAPIを呼び出せるようになっています。もちろん、アップロードやリストの取得等も簡単です。

Tableやキューについても基本は同じだと思うのですが、個人的にBlobストレージ以外を扱う機会がなかったのでBlobオンリーになっています。機会があればテーブル、キューに対応したモジュールも書けたらと思っています。

ちょっと面白いのはマルチスレッドに対応していて、examples/blobsync.pl のサンプルを使うと以下のような感じでローカルのディレクトリとBlobを高速に同期できる点です。

/usr/bin/perl examples/blobsync.pl --account your_account --accesskey you_primary_access_key --direction upload --path container_name/directory_name --directory /path/to/local/directory --use_thread 10

--direction download とすれば逆方向の同期も可能。AZCopyはMacでは使えないですし、もちろん CloudXplorer もMacでは使えないため、毎回 VMWareで Windowsを立ち上げて作業していたのですが、あまりに面倒だし、個人的には Movable Type を仕事で使うことが多いのに PHP のSDKはあるけどPerl SDKはないし... というので書いた次第。サンプル等を色々書くつもりだったのですが、それはまたいずれ。

ちなみに、以下のページ「COMPLETED」になっていますね。早くPerlも正式サポートされて欲しいものです。

Mac OS X用のBlobストレージクライアントソフトを作ってみたお話

ようやくここから今回の本題に入るのですが、Perlモジュール作ったことでコマンドラインからBlobの読み書きができるようにはなった。とはいえ、コマンドラインツールです。サイトのメンテナンスをする人、例えばFTPクライアントでファイル転送することはできる、くらいのスキルの人(つまり、一般的なWeb制作者(not 開発者))がコマンドライアンから作業って敷居が高いではないですか。で、ふと思い立ってMac OS X用のBlobストレージクライアントソフトを作ってみようと思ったのでした。

百聞は一見に如かず、まずは動画を。

できること

  • マルチアカウント対応
  • コンテナの作成
  • フォルダの作成
  • ファイル/フォルダのアップロード(ドラッグ&ドロップ)
  • ファイル/フォルダのダウンロード(ドラッグ&ドロップ)
  • コンテナのACL(アクセス権)の設定
  • コンテナ/Blobのメタデータの確認、編集、追加、削除
  • Blobの直接編集
  • Blobのクイックルック
  • コンテクストメニュー対応(コントロール+クリック又は右クリック)

使い方

  • 「設定」タブをクリックして、Blobストレージのアカウントとプライマリアクセスキーを入力、保存
  • 「ブラウザ」タブをクリックして「コンテナの一覧」ボタンをクリック
  • あとは、普通? のFTPクライアントのようにダブルクリックでコンテナ、フォルダを開く、ドラッグ&ドロップでファイルのアップロード、ダウンロードなどの操作が行えます。
  • .. で 一つ上の階層への移動、 . でリロード
  • キーボードショートカット対応(←(ひとつ戻る) →(ひとつ進む)、I (情報を見る)、O(開く)、等)
  • クイックルック(スペースキーまたはコマンド+Y)

動画をご覧いただいてわかる通り、そこそこのスピードは出ていると思います。

ソフトウェアのダウンロード(このソフトウェアはβ版です)

現在App Storeに登録すべく、Apple Developer Programへの登録申請を行おうとしたのですが、DUNS 情報の修正のところで時間がかかっているため(2週間くらいかかるようです)、未登録、サンドボックス化がまだなので、野良? アプリ段階ですが(最初の起動時には右クリック(コントロール+クリックから起動する必要があります))、一応置いておきます。ご利用は自己責任でお願いします。

ちなみに、Blobへのアップロード、コンテナ作成、コンテナのアクセス権設定などはすべてこのソフト自身でやりました。

と、いうことでPerlモジュールの話し改め Mac OS X用のAzure Blobクライアントソフトの紹介になりましたが、このソフトの処理部分は実はこのPerlモジュールが使われています。XojoでGUIを被せただけで、実際のAPIを利用したアクセスはPerlが担っています。興味のある方は以下のエントリーもどうぞ。

追記 : 動かなかった(エラー)という方がいたので、エラーについてある程度の詳細が出るようにしたバージョンに差し替えました。宜しければフィードバックいただけると幸いです。junnama[at].alfasado.jp

Xojoネタを続けます。備忘録、忘れていたことの覚え書き、何でもいいや。

動的なメニュー、例えばスタイル付きテキストエディタやワープロのフォントメニューの例とかで、Tutorial & Developer's Guideに書いてあるんだけど(REALBasic3のを引っ張りだしてきたのだ)、肝心のメニューハンドラのコードが書いてない。メニューアイテムを配列にするには indexに 0 を指定する(以下のキャプチャのように)

メニューエディタ(メニューアイテムを配列に指定)

動的にメニューを追加するには、例えば以下のように。


Dim m As MenuItem
m = new MenuWindowProperties
m.Text = Foo
m.Tag = Bar
m.Visible = True
m.Enabled = True

で、具体的にメニューハンドラを書こうとして...

メニューハンドラの入力画面

普通は書いてあるじゃん。以下のような感じで...

コードエディタに引数と返り値の型が記述してある

答え : indexに選択されたメニューのindex(Integer)が入っているのでした。


MenuWindowProperties(index As Integer) As Boolean

と、なるべきだな。昔のコード引っ張りだして見つけたから良いようなものの...

引き続きXojo(とMacOS)ネタ。

要するに、こういうことをやりたいとき、Dropされたアイテムのパスを得られないとどうしようもないのですが、DragItem.Destinationというプロパティでこれが実現できる、とあります(サポートに教えてもらった)。但しこれはMac OS限定ということでWindows、Linuxではダメらしい。今回はMac OS限定の開発なので、これでできると思いきや、Carbonでは動くがCocoaではこの値がとれない(現段階でサポートに確認中)。で、このためだけにCarbonにするのも悔しかったので、どうにかやってみようと。

アプリのリストボックスからアイテムをドラッグ&ドロップしたい

アプローチその1. エイリアスを作ってTimerで監視

MakeAliasというAppleScriptを作ってテンポラリ領域に作ったファイルのエイリアスを作り、Timerを使ってドラッグされた先のパスを見る。


on run (theOriginal)
    set theOriginal to POSIX file theOriginal
    set theOriginal to alias theOriginal
    tell application "Finder"
        set theFolder to folder of theOriginal
        make new alias file at theFolder to theOriginal returning theResult
        set theResult to theResult as string
    end tell
    --set theResult to POSIX path of theResult
    return theResult
end run

オリジナルのアイテムのパスを得るためにResolveAliasというAppleScriptを作る。

on run (theOriginal)
    set theOriginal to alias theOriginal
    tell application "Finder"
        set theResult to (original item of theOriginal) as alias
    end tell
    set theResult to POSIX path of theResult
    return theResult
end run

結論から言うと、これはNG。またもやCarbonで動くがCocoaでアウトなのでした。DragItem.FolderItemがMoveではなくCopyになってしまう。なので、オリジナルのパスはテンポラリ領域のファイルとなる(ただ、せっかくやったのでこうやって書いておくのだ。きっとどこかで誰かが、あるいは自分の役に立つので)。POSIX path というのが使えるのを学びましたよ(PathをUnixタイプのNative Pathに変換してくれる)。

アプローチその2. Spotlightを使ってTimerで監視

こちらは上手く行きました。ちょっとだけタイムラグが出るのが玉にきずですが。

Dim command As String = "mdfind -name " + DraggingItem
Dim Sh As New Shell
Sh.Execute( command )
Dim Res As String = Sh.ReadAll

要するに、存在しないファイル名(乱数から生成したMD5ハッシュ値とタイムスタンプとかから組み立てたファイル名)でテンポラリファイルを作り、不可視にしてDragさせる。同時にTimerを走らせる。If System.MouseDown = False Then (マウスが押されていない状態になった時)に上記を実行する(mdfindというコマンドがあって、こいつは Spotlight のコマンドラインツールなのです。

多少のタイムラグが出るものの、これでドラッグされた不可視アイテムのパスが得られるので、その親(フォルダ)に対して選択されたアイテムをダウンロードさせる、という方法。存在しないファイル名として作ったファイル名が本当に存在しないかどうかも同じコマンドで得られるので。

ただ、やっぱりちゃんと DragItem.Destination なりでサポートして欲しいと思ったのでした。とりあえずCocoa化はできた。Carbonはやっぱり速度とかLook & Feelが美しくないので、やっぱり作るならCocoaで作りたいよね。

先月目出たくCPANデビューを果たしたり色々あったのですが、ご無沙汰でブログ書けてませんでした...

メモも兼ねて。久しぶり、そう、もう10年近く振りになるのか、デスクトップアプリを作ってみたくなったので、Xojoを落として使ってみました。以前はREALBasic(それより前はCross Basic)、その後REAL Studioと改名され、現在はXojo(ソージョーと呼ぶらしい)。

その? CPANに上げたPerlモジュールを社員に使わせたいんだけど、黒い画面が苦手な女の子(すでに子、じゃないけど)だったら? やっぱりGUIのアプリが欲しくなるよね。作れるかな? というのがきっかけでした。Xojo にはShellというクラスがあって、Mac/Windows/Linuxともにシェルコマンドが叩けるというので、Perlでスクリプトを書いてそれを叩けば(結果をうけとれれば)作れるんじゃね? と思って書いたらちゃんと動きました。MacOSのアプリケーションの実体は「フォルダ」なので、アプリケーションパッケージ内にPerlモジュールをコピーしてしまえば配布する時はそのままでいいので。

アプリのスクリーンショット

作成したのはWindows Azure のBlobストレージのクライアントアプリ。ファイルやフォルダをドラッグ&ドロップでアップロード、ダウンロードできたり。Azure BlobのAPIはXMLを返すのだけど、XojoではXMLの処理ができるのでAPIからの値の受け渡しのみPerlでやればあとはXojo側で実装できます。1日程あれば基本的な動作確認ができることろまで作れる(その後が色々大変なのではあるけれども)。


  CONST DEBUG_PATH = "/Users/alfasado/Desktop/MyApp"
  Dim App as new Application
  Dim SelfPath As FolderItem
  Dim Debug As Boolean
  SelfPath = App.ExecutableFile // 実行している自身のパスを得る
  if CountFields( SelfPath.Name, "." ) = 2 And NthField( SelfPath.Name, ".", 2 ) = "debug" Then
    Debug =True // デバッグ時にはモジュールのパスを指定
  End If
  Dim Path As String
  if Debug = True Then
    Path = DEBUG_PATH
  Else
    Dim Name As String = SelfPath.Name + ".app"
    SelfPath = GetFolderItem( SelfPath.NativePath ).parent
    SelfPath = SelfPath.Child( Name )
    SelfPath = GetFolderItem( SelfPath.NativePath )
    Path = SelfPath.ShellPath // スペース等をバックスラッシュでエスケープしたパスが返る
    MsgBox( Path )
    // /Users/alfasado/Desktop/MyApp/\:Users\:alfasado\:Desktop\:MyApp\:My\ Application.app
    Path = NthField( Path, "\:", 1 ) + Name + "/Contents/MacOS"
  End If
  Dim Cmd As String = "cd " + Path + "; perl src/app.pl"
  Dim Sh As New Shell
  Sh.Execute( Cmd )
  MsgBox( Sh.Result )
  // app.plで printした結果が取得できる

あとは、ビルドしたあとで、パッケージにモジュールをコピーすればOK。

cd /Users/alfasado/Desktop/MyApp/Builds\ -\ AZBlobClient0.5.carbon.Xojo_binary_project/Mac\ OS\ X\ \(Intel\)/AZBlobClient.app/Contents/MacOS/
cp /Users/alfasado/Desktop/MyApp/lib.zip lib.zip
cp /Users/alfasado/Desktop/MyApp/src.zip src.zip
unzip lib.zip
unzip src.zip
rm lib.zip
rm src.zip

MacOSにはPerlとか標準で入っているから、普段軽量プログラミング言語を使っていてデスクトップツールを作りたい人なんかにXojoいいかも。REAL Studio一時不安定なイメージを持っていたけど、1週間程使い込んだ印象では安定して動きます。ライセンス購入したら(デスクトップライセンスを購入したよ)日本語のサポートも受けられますよ。

追記。こんな感じのパスになるのは、


    // /Users/alfasado/Desktop/MyApp/\:Users\:alfasado\:Desktop\:MyApp\:My\ Application.app

GetFolderItemの引数がまずかったせいらしい。FolderItem.PathTypeAbsolute(0), FolderItem.PathTypeShell(1), FolderItem.PathTypeURL(2), FolderItem.PathTypeNative(3) という定数があって、2つめの引数に指定するときれいに取れる。この場合は、3(FolderItem.PathTypeNative)を指定。


GetFolderItem( SelfPath.NativePath, 3, )

Facebook

Twitter

このアーカイブについて

このページには、過去に書かれたブログ記事のうちXojoカテゴリに属しているものが含まれています。

次のカテゴリはソーシャルメディアです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 6.2.6