アラートダイアログ上にTable Viewを表示するGUI部品を提供するAppleScriptです。Edama2さんから投稿していただいたものです。
箱庭テーブルビューシリーズは、macOS標準装備の「choose from list」コマンドでは選択肢が増えたときに使いにくいので、その代替品としてTable Viewを用いたポップアップしない単項目の選択UIを実装したものでした。
▲最初のバージョン。みなぎる使い捨て感
それが途中から「Myriad Tablesが使えない場面(Framework入りの高機能ライブラリのため、利用できないケースが発生)に使う使い捨て部品」に進化。
コードサイズの投げやりなまでの小ささと、いつもどおりのプレーンな読み味から改造のためのベースキットとして活用されまくり、派生品や亜種が内外でいろいろ発生しています(きっと知らないところで改変版が作られているはず)。
sdefをつけてライブラリ化したり、色付きダイアログなどいろいろキワモノが派生しましたが、実用性を考慮して半透明ウィンドウではなく通常ウィンドウとして表示する方向に変わりつつあります。
Version | Author | Date | Description |
v1 | Piyomaru | 2019/2/14 | For choose from list without popup menu. One field selection UI. |
v2 | Piyomaru | 2019/2/14 | |
v3 | Piyomaru | 2019/2/14 | |
v4 | Piyomaru | 2019/7/11 | Table user interface for item selection |
v5_e | KniazidisR | 2019/11/2 | Separate display dialog stage into making and displaying |
v5_m | Piyomaru | 2019/12/23 | Filtering by multiple field |
v6 | Piyomaru | 2019/12/24 | Search Field Added |
v7 | Edama2 | 2019/12/240 | Vertical & horizontal resizing |
v8 | Edama2 | 2019/12/25 | Record sorting & Field re-ordering |
以下、v8についてのedama2さんからの説明文です。
コラムのヘッダのクリックでテーブルのソート ドラッグ&ドロップによる行の並び替え 行の複数選択 ができるようになりましたが、ヘッダのソート状態のときにD&Dの並び替えはできません。 飛び飛びに複数行選択しても並び替えできますが、たまにズレている気がします...(汗) ArrayControllerを使用したのが機能過剰と思われたかもしれませんが、まずXCode上で一度組んだものを逆移植しているのでそんなに深い意味はないです。 でもソート方法を試行錯誤して調べていたら、Cocoa bindingをコーディングから設定できることがわかった(よく考えれば当たり前ですが)のが、何か壁を一枚破った気がしました。
Cocoa bindingがプログラムから動的に実行できることは、調査して知っていましたが、実際にAppleScriptのコードとして記述されたものは見たことがありませんでした(Mac Scripterのフォーラムを漁ると出てくるのかも?)。
### 表示内容をNSArrayControllerにバインディング bind_toObject_withKeyPath_options_(current application’s NSValueBinding, my _data_source, "arrangedObjects." & aTitle, missing value)
ここの部分の記述は、クリスマスプレゼントとして楽しませていただきます。Cocoa Bindingなんて、Xcode上でGUI部品とイベント受信部分のハンドラとの接続をただ漫然とヒモでつなぐ作業をしていただけだったので、Cocoa Binding Referenceなんてはじめて細部にわたって眺めましたわー。
▲起動状態。フィールドの横サイズそろえ、レコード数により表UIの縦サイズが調整されている
▲Specsフィールドを中央に持ってきた。ちゃんとUIが細かいアニメーション動作を行う
▲選択行をドラッグ&ドロップで移動。複数行も移動可能。ただし、フィールド指定のソート状態を有効にすると行移動が効かないという
AppleScript名:アラートダイアログ上にTable Viewを表示 v8_ソート、並び替え、複数選択.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 property _data_type : (current date) as text –do shell script "uuidgen" –>ユニークな文字列ならなんでもいい? 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 (my _data_source)’s setSelectionIndex:0 ## NSTableView tell current application’s NSTableView’s alloc() tell initWithFrame_(current application’s CGRectZero) setAllowsEmptySelection_(false) setAllowsMultipleSelection_(true) setDataSource_(me) setDelegate_(me) setDoubleAction_("doubleAction:") setGridStyleMask_(current application’s NSTableViewSolidVerticalGridLineMask) setSelectionHighlightStyle_(current application’s NSTableViewSelectionHighlightStyleRegular) setTarget_(me) setUsesAlternatingRowBackgroundColors_(true) registerForDraggedTypes_({my _data_type}) setDraggingSourceOperationMask_forLocal_(current application’s NSDragOperationCopy, false) 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 sortDescriptor to (current application’s NSSortDescriptor’s sortDescriptorWithKey:aTitle ascending:true selector:"compare:") setSortDescriptorPrototype_(sortDescriptor) ### 表示内容をNSArrayControllerにバインディング bind_toObject_withKeyPath_options_(current application’s NSValueBinding, my _data_source, "arrangedObjects." & aTitle, missing value) 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 date on retrieveData(aTableObj) set aIndexSet to aTableObj’s selectedRowIndexes() set my _retrieve_data to ((my _data_source)’s arrangedObjects()’s objectsAtIndexes:aIndexSet) as list –setSelectionIndexes:aIndexSet log result 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 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 ## ヘッダをクリックした時は何もしない if (sender’s clickedRow()) is -1 then return 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: # Drag Operation Method ## ドラッグを開始(ペーストボードに書き込む) on tableView:aTableView writeRowsWithIndexes:rowIndexes toPasteboard:pboard –log "writeRowsWithIndexes" set aData to current application’s NSKeyedArchiver’s archivedDataWithRootObject:rowIndexes pboard’s declareTypes:{my _data_type} owner:(missing value) pboard’s setData:aData forType:(my _data_type) return true end tableView:writeRowsWithIndexes:toPasteboard: ## ドラッグ途中 on tableView:aTableView validateDrop:info proposedRow:row proposedDropOperation:operation –log "validateDrop" ### 列の間にドラッグ if (operation is current application’s NSTableViewDropAbove) then return current application’s NSDragOperationMove end if ### 項目の上にドラッグ set aTypes to info’s draggingPasteboard’s types() if (aTypes’s containsObject:(my _data_type)) as boolean then return current application’s NSDragOperationNone end if return current application’s NSDragOperationNone end tableView:validateDrop:proposedRow:proposedDropOperation: ## ドラッグ終了 on tableView:aTableView acceptDrop:info row:row dropOperation:operation –log "acceptDrop" set pboard to info’s draggingPasteboard() set rowData to pboard’s dataForType:(my _data_type) set rowIndexes to current application’s NSKeyedUnarchiver’s unarchiveObjectWithData:rowData if (rowIndexes’s firstIndex()) < row then set row to row – (rowIndexes’s |count|()) end if set aRange to current application’s NSMakeRange(row, rowIndexes’s |count|()) set aIndexSet to current application’s NSIndexSet’s indexSetWithIndexesInRange:aRange tell my _data_source set anObj to content()’s objectsAtIndexes:rowIndexes removeObjects_(anObj) insertObjects_atArrangedObjectIndexes_(anObj, aIndexSet) rearrangeObjects() end tell return true end tableView:acceptDrop:row:dropOperation: end script ## tell (make MyDialog) return call(aMainMes, aSubMes, optionRec) end tell end chooseData |