先日「macOS Native」のイベント後に秋葉原で顔見知りのScripter3人でカレー食べて帰ってきました。その際に、アラートダイアログ上に作成した箱庭User Interface類の話で盛り上がっていました。「意外と使える」「自分でもいろいろ作ってる」「公開してよ」といったような。
本Scriptはその際の話を受けてEdama2さんが作っている試作品の、さらに試作品になるわけですが、プログラム内容が自分とは別の価値体系で清書し直されており、あいかわらずロジックとコードの美しさにうならされます。また、「本来倒すべき敵」に向けての実装が行われているので、単にテーブルビューを表示するだけには少々機能過剰になっています。
以下、Edama2さんによる投稿内容です。
タイトルは「アラートダイアログ上にTable Viewを表示 v7_サイズ調整あり」 主な変更点は 表示する内容に合わせてviewのサイズを調整 レコードのラベルをヘッダのタイトルにする Table ViewをダブルクリックするとOKボタンを押す setAllowsEmptySelection: をfalseにして1行目を選択するようにした
データ作成部分が詰め込み効率優先ではなく可読性を高めた作りになっているのが、Edama2さんらしいところです。また、tellブロックをCocoa Scriptingにも多用しているのも特徴です。
ロジックが読みやすい一方で、Cocoa Framework相手にここまで見やすく書くのは、相当の努力が必要なはずで、Cocoa Scripting時に省いてしまいがちな「清書」という1手間が入っていることを感じさせます。
本Scriptはハンドラ(サブルーチン)が前の方に配置される記法です。AppleScriptはもともとハンドラ宣言部を前に書く言語なので、世界的に見るとこちらが主流です(Shane Stanleyですらハンドラ宣言部は前に書きます)。逆に、自分が他の言語に合わせてハンドラ宣言部を後ろに配置するのは、全体から見ると異端といえます(他の言語と書き方が違いすぎるのはどうかと思って、ハンドラ宣言部を後ろに書いています)。
たしかに、自分の箱庭テーブルビューダイアログではカラム幅とテーブルの縦サイズは一切手をつけていなかったので、縦横幅高さの自動調整が入ると、格段に見やすくなります。
Cocoaの機能を呼び出すAppleScriptにおいても、書きこなれてきたことによりハンドラ内にScript文を書いてさらに内部にハンドラを記述するといったような、論理分割やら階層構造化が本Scriptにおいて多用されています。そのあたり、applescript-stdlib(2015)で見かけましたが、本Scriptのように控えめな利用だと安心できます。
本Script内には継続記号(「¬」)が多用されていますが、macOS日本語ユーザー環境上でScript Debuggerを使っていると、構文確認時にこれがひっかかってエラーになることがあります(というか、実際にウチでこれを編集してなっています)。継続記号がひっかかって構文確認(コンパイル)できないようであれば、いっそ継続記号をすべて削除すべきでしょう。継続記号に「可読性を上げる」以外の機能はないため、削除しても無害です。
AppleScript名:アラートダイアログ上にTable Viewを表示 v7_サイズ調整あり.scpt |
on run set aTableList to {} set aTableList’s end to {|Name|:"MacBook Air", Price:"119,800円", Specs:"1.6GHzデュアルコアIntel Core i5(Turbo Boost使用時最大3.6GHz)、4MB L3キャッシュ"} set aTableList’s end to {|Name|:"MacBook Pro 13", Price:"139,800円", Specs:"1.4GHzクアッドコアIntel Core i5(Turbo Boost使用時最大3.9GHz)、128MB eDRAM"} set aTableList’s end to {|Name|:"MacBook Pro 15", Price:"258,800円", Specs:"2.6GHz 6コアIntel Core i7(Turbo Boost使用時最大4.5GHz)、12MB共有L3キャッシュ"} set aTableList’s end to {|Name|:"Mac mini", Price:"122,800円", Specs:"3.0GHz 6コアIntel Core i5 Turbo Boost使用時最大4.1GHz 9MB共有L3キャッシュ"} set aTableList’s end to {|Name|:"iMac 21.5", Price:"120,800円", Specs:"2.3GHzデュアルコアIntel Core i5(Turbo Boost使用時最大3.6GHz)"} set aTableList’s end to {|Name|:"iMac 4K 21.5", Price:"142,800円", Specs:"3.6GHzクアッドコアIntel Core i3"} set aTableList’s end to {|Name|:"iMac 5K 27", Price:"198,800円", Specs:"3.0GHz 6コア Intel Core i5(Turbo Boost使用時最大4.1GHz)"} set optionRec to {aTableList:aTableList} set optionRec to optionRec & {aSortOrder:{column1:"Name", column2:"Price", column3:"Specs"}} set aMainMes to "項目の選択" set aSubMes to "適切なものを以下からえらんでください" set dateObj to my chooseData(aMainMes, aSubMes, optionRec) end run # アラートダイアログでtableviewを表示 on chooseData(aMainMes, aSubMes, optionRec) script MyDialog property parent : AppleScript use AppleScript use scripting additions use framework "Foundation" property _retrieve_data : missing value on make set aClass to me script property parent : aClass end script end make ## ダイアログの呼び出し on call(aMainMes, aSubMes, optionRec) set paramObj to {myMessage:aMainMes, mySubMessage:aSubMes, myOption:optionRec} parent’s performSelectorOnMainThread:"raize:" withObject:paramObj waitUntilDone:true if (my _retrieve_data) is missing value then error number -128 return (my _retrieve_data) end call ## ダイアログの生成 on raize:paramObj ### set mesText to paramObj’s myMessage set infoText to paramObj’s mySubMessage set paramObj to paramObj’s myOption ### set up view set {theView, makeObj} to my makeContentView(paramObj) ### set up alert tell current application’s NSAlert’s new() setMessageText_(mesText) setInformativeText_(infoText) addButtonWithTitle_("OK") addButtonWithTitle_("Cancel") setAccessoryView_(theView) tell |window|() setInitialFirstResponder_(theView) end tell #### show alert in modal loop if runModal() is (current application’s NSAlertSecondButtonReturn) then return end tell ### retrieve date my retrieveData(makeObj) end raize: ## ContentView を作成 property _data_source : missing value on makeContentView(paramObj) ## 準備 set aList to (paramObj’s aTableList) as list set keyRec to (paramObj’s aSortOrder) as record set keyDict to (current application’s NSDictionary’s dictionaryWithDictionary:keyRec) set my _data_source to current application’s NSArrayController’s new() repeat with anItem in aList set aDict to (current application’s NSDictionary’s dictionaryWithDictionary:anItem) ((my _data_source)’s addObject:aDict) end repeat ## NSTableView tell current application’s NSTableView’s alloc() tell initWithFrame_(current application’s CGRectZero) setAllowsEmptySelection_(false) setDataSource_(me) setDelegate_(me) setDoubleAction_("doubleAction:") setGridStyleMask_(current application’s NSTableViewSolidVerticalGridLineMask) setSelectionHighlightStyle_(current application’s NSTableViewSelectionHighlightStyleRegular) setTarget_(me) setUsesAlternatingRowBackgroundColors_(true) set thisRowHeight to rowHeight() as integer set aTableObj to it end tell end tell ## NSTableColumn ### Columnの並び順を指定する set viewWidth to 0 set columnsCount to keyDict’s |count|() repeat with colNum from 1 to columnsCount set keyName to "column" & colNum as text set aTitle to (keyDict’s objectForKey:keyName) tell (current application’s NSTableColumn’s alloc()’s initWithIdentifier:(colNum as text)) tell headerCell() setStringValue_(aTitle) set thisHeaderHeight to cellSize()’s height end tell set aTableColumn to it end tell ### Columnの横幅を調整 tell aTableObj addTableColumn_(aTableColumn) –reloadData() set colWidthMax to 0 set rowCount to numberOfRows() repeat with rowNum from 1 to rowCount tell preparedCellAtColumn_row_(colNum – 1, rowNum – 1) set thisWidthSize to cellSize()’s width –log result if thisWidthSize is greater than or equal to colWidthMax then set colWidthMax to thisWidthSize end tell end repeat end tell –log colWidthMax set colWidthMax to colWidthMax + 5 –> 5は余白 tell aTableColumn setWidth_(colWidthMax) end tell set viewWidth to viewWidth + colWidthMax end repeat ## NSScrollView ### Viewの高さを計算 set viewHeight to (thisRowHeight + 2) * rowCount + thisHeaderHeight –> 2を足さないと高さが合わない set vSize to current application’s NSMakeRect(0, 0, viewWidth + 10, viewHeight) –> 10を足すといい感じの横幅になる ### Viewを作成 tell current application’s NSScrollView’s alloc() tell initWithFrame_(vSize) setBorderType_(current application’s NSBezelBorder) setDocumentView_(aTableObj) –setHasHorizontalScroller_(true) –setHasVerticalScroller_(true) set aScroll to it end tell end tell return {aScroll, aTableObj} end makeContentView ## retrieve data on retrieveData(aTableObj) set aRow to aTableObj’s selectedRow() log result if aRow is -1 then set my _retrieve_data to {} else set my _retrieve_data to ((my _data_source)’s arrangedObjects()’s objectAtIndex:aRow) as record end if end retrieveData # NSTableViewDatasource on numberOfRowsInTableView:aTableView –log "numberOfRowsInTableView:" return (my _data_source)’s content()’s |count|() end numberOfRowsInTableView: # NSTableViewDelegate on tableView:aTableView objectValueForTableColumn:aColumn row:aRow –log "objectValueForTableColumn" set keyName to aColumn’s headerCell()’s title() set aDict to (my _data_source)’s arrangedObjects()’s objectAtIndex:aRow set aStr to aDict’s objectForKey:keyName –log result as text return aStr end tableView:objectValueForTableColumn:row: # テーブル内のセルが編集できるか on tableView:aTableView shouldEditTableColumn:aColumn row:aRow return false end tableView:shouldEditTableColumn:row: # テーブル内をダブルクリックしたらOKボタンを押す on doubleAction:sender –log "doubleAction" –log sender’s |className|() as text set theEvent to current application’s NSEvent’s ¬ keyEventWithType:(current application’s NSEventTypeKeyDown) ¬ location:(current application’s NSZeroPoint) ¬ modifierFlags:0 ¬ timestamp:0.0 ¬ windowNumber:(sender’s |window|()’s windowNumber()) ¬ context:(current application’s NSGraphicsContext’s currentContext()) ¬ |characters|:return ¬ charactersIgnoringModifiers:(missing value) ¬ isARepeat:false ¬ keyCode:0 current application’s NSApp’s postEvent:theEvent atStart:(not false) end doubleAction: end script ## tell (make MyDialog) return call(aMainMes, aSubMes, optionRec) end tell end chooseData |