Archive for the 'NSAutoreleasePool' Category

2012/06/13 おかえり(シンプル版)

読者の方(edama2さん)からの投稿です。以前に私がAppleScript Studioで作成して公開していた(る?)、スリープ解除検知&ユーザー通知アプリケーション「おかえり」の簡略版をMac OS X 10.7から作れるようになったAppleScriptエディタベースのAppleScriptObjCで作られました。

yyyeyoyycyaye-2012-06-13-02922.jpeg

yyyeyoyycyaye-2012-06-10-235739.jpeg

投稿していただいてから公開するまでに時間がかかってしまいましたが……実は、AppleScriptエディタベースのASOCのHTML書き出しツールを用意していなかったためで……ようやく、書き出し用のツール(AppleScriptで記述)を準備しました。

——
<edama2さんより>
今日のネタは「おかえり(シンプル版)」です。なかなか更新されない「おかえり」にいらだちを覚え、ついに自分で作り……というのは冗談で、「Cocoa-applescript aplet」の習作として作ってみました。

スリープの復帰の通知を受け取り、半透明のウインドウの表示と音を鳴らすだけです。誕生日とか正月だけの特別なウィンドウはありません。

ウインドウの色と音を変更できるようにしたいところですが、Xcodeを使わずコードだけでウィンドウを作るのは結構面倒だったのとシンプル版ということで省略しました。オリジナルの「おかえり」から「Twentieth Anniversary Macintosh.aiff」だけ使用させていただきました。

最初、同じスプリクト内にカスタムビューのコード(MyView.scpt)を書いていたのですが、初期化の際にエラーが出て難儀しました。どうやら別ファイルにしないと上手く初期化してくれないようです。

作成は10.7.4、テストは10.7.4と10.6.8でしました。ASOC Scriptの新規作成は10.7.4でないとできませんが、編集は書き方によっては10.6.8でもできました。

→ アプレット(編集可能)のダウンロード
</edama2さんより>

スクリプト名:おかえり(シンプル版)
property _sound_name : “Twentieth Anniversary Macintosh”

on run
  #通知の登録
  
tell current application’s NSWorkspace’s sharedWorkspace()
    tell notificationCenter()
      addObserver_selector_name_object_(me, “okaeri:”, “NSWorkspaceScreensDidWakeNotification”, missing value)
    end tell
  end tell
end run

on quit
  current application’s NSNotificationCenter’s defaultCenter()’s removeObserver_(me)
  
continue quit
end quit

#ウィンドウを表示→待機→閉じる
on okaeri_(sender)
  set pool to current application’s NSAutoreleasePool’s new() –>意味があるかよくわからないけどとりあえず。
  
  
copy makeWin() to okaeriWindow
  
  
set filePath to current application’s NSBundle’s mainBundle()’s pathForResource_ofType_(_sound_name, “aiff”)
  
current application’s NSSound’s alloc()’s initWithContentsOfFile_byReference_(filePath, true)’s play()
  
  
delay 5
  
  
closeWin_(okaeriWindow)
  
  
pool’s drain() –release()
end okaeri_

#ウィンドウを作成
on makeWin()
  set aScreen to current application’s NSScreen’s mainScreen()
  
set aFrame to {{0, 0}, {800, 250}}
  
set aBacking to current application’s NSBorderlessWindowMask
  
set aDefer to current application’s NSBackingStoreBuffered
  
  
tell current application’s NSWindow’s alloc()
    tell initWithContentRect_styleMask_backing_defer_screen_(aFrame, aBacking, aDefer, false, aScreen)’s autorelease()
      
      
#カスタムビュー
      
tell current application’s MyView’s alloc()
        tell initWithFrame_(aFrame)
          setNeedsDisplay_(true)
          
set aView to it
        end tell
      end tell
      
      
set {origin:{x:x, y:y}, |size|:{width:w, height:h}} to contentView()’s |bounds|()
      
set titleHeight to 26
      
set y2 to h - titleHeight - (titleHeight / 6)
      
      
#タイトルバー
      
set messageText to “スリープ復帰を検出しました(” & (current date) & “)” as text
      
tell current application’s NSTextView’s alloc()
        tell initWithFrame_({{0, y2}, {w, titleHeight}})
          insertText_(messageText)
          
setAlignment_(current application’s NSCenterTextAlignment)
          
setDrawsBackground_(false)
          
setEditable_(false)
          
setFont_(current application’s NSFont’s titleBarFontOfSize_(13))
          
setSelectable_(false)
          
setTextColor_(current application’s NSColor’s whiteColor())
          
aView’s addSubview_(it)
        end tell
      end tell
      
      
set y3 to h - titleHeight
      
set y3 to h - y3 - (y3 / 6)
      
      
#メッセージビュー
      
set myName to current application’s NSProcessInfo’s processInfo()’s environment()’s valueForKey_(”LOGNAME”) as text
      
set messageText to myName & “さん” & return & “おかえりなさい”
      
tell current application’s NSTextView’s alloc()
        tell initWithFrame_({{0, y3}, {w, y2}})
          insertText_(messageText)
          
setAlignment_(current application’s NSCenterTextAlignment)
          
setDrawsBackground_(false)
          
setEditable_(false)
          
setFont_(current application’s NSFont’s fontWithName_size_(”HiraMinPro-W3″, 72))
          
setSelectable_(false)
          
setTextColor_(current application’s NSColor’s whiteColor())
          
aView’s addSubview_(it)
        end tell
      end tell
      
      
#ウィンドウの設定
      
setBackgroundColor_(current application’s NSColor’s clearColor())
      
setContentView_(aView)
      
setDelegate_(me)
      
setDisplaysWhenScreenProfileChanges_(true)
      
setHasShadow_(true)
      
setIgnoresMouseEvents_(not false)
      
setLevel_((current application’s NSScreenSaverWindowLevel) + 10000)
      
setOpaque_(false)
      
setReleasedWhenClosed_(true)
      
|center|()
      
makeKeyAndOrderFront_(me)
      
      
return it
    end tell
  end tell
end makeWin

#ウィンドウを閉じる
on closeWin_(aWindow)
  tell aWindow
    repeat with n from 10 to 1 by -1
      setAlphaValue_(n / 10)
      
delay 0.02
    end repeat
    
|close|()
  end tell
end closeWin_

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

スクリプト名:MyView
script MyView
  property parent : class “NSView”
  
  
on drawRect_(rect)
    
    
–log my |bounds|()
    
set {origin:{x:x, y:y}, |size|:{width:w, height:h}} to my |bounds|()
    
set titleHeight to 26
    
    
set thePath to current application’s NSBezierPath’s bezierPath()
    
    
#タイトルバー
    
set aFrame to {{0, h - titleHeight}, {w, titleHeight}}
    
set aPath to current application’s NSBezierPath’s bezierPathWithRect_(aFrame)
    
set strartColor to current application’s NSColor’s blackColor()’s colorWithAlphaComponent_(0.4)
    
set endColor to current application’s NSColor’s blackColor()’s colorWithAlphaComponent_(0.8)
    
tell current application’s NSGradient’s alloc()
      tell initWithStartingColor_endingColor_(strartColor, endColor)
        drawInBezierPath_angle_(aPath, 270)
      end tell
    end tell
    
thePath’s appendBezierPath_(aPath)
    
    
#メッセージ
    
set aFrame to {{0, 0}, {w, h - titleHeight}}
    
set aPath to current application’s NSBezierPath’s bezierPathWithRect_(aFrame)
    
set strartColor to current application’s NSColor’s blueColor()’s colorWithAlphaComponent_(0.4)
    
set endColor to current application’s NSColor’s blueColor()’s colorWithAlphaComponent_(0.8)
    
tell current application’s NSGradient’s alloc()
      tell initWithStartingColor_endingColor_(strartColor, endColor)
        drawInBezierPath_angle_(aPath, 270)
      end tell
    end tell
    
thePath’s appendBezierPath_(aPath)
    
  end drawRect_
  
end script

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

スクリプト名:CocoaAppletAppDelegate.scpt

– CocoaAppletAppDelegate.applescript
– Cocoa-AppleScript Applet

– Copyright 2011 {Your Company}. All rights reserved.

– This application delegate emulates the OSA script applet by loading “main.scpt” from the
– “Scripts” folder in the application resources and invoking the traditional run/open/reopen/quit
– handlers in response to Cocoa application delegate methods being called.

– This is provided in source form so that you may customize or replace it if your needs go
– beyond the basic applet handlers.

– Some of these methods must guard against re-entrancy, because invoking the main.scpt
– handler may end up invoking the event handler inherited from the current application,
– which calls the application delegate’s method again.

script CocoaAppletAppDelegate
  property parent : class “NSObject”
  
property mainScript : missing value – the applet’s main.scpt
  
property didOpenFiles : false – true = the application opened documents during startup
  
property isOpeningFiles : false – re-entrancy guard: true = in the process of opening files
  
property isReopening : false – re-entrancy guard: true = in the process of re-opening
  
property isQuitting : false – re-entrancy guard: true = in the process of quitting
  
  
on applicationWillFinishLaunching_(aNotification)
    – Insert code here to initialize your application before any files are opened
    
    
– Emulate an OSA Applet: Load the main script from the Scripts resource folder.
    
try
      set my mainScript to load script (path to resource “main.scpt” in directory “Scripts”)
    on error errMsg number errNum
      – Perhaps this should silently fail if it can’t load the script; that way, a Cocoa applet
      
– can just have Cocoa classes and no main.scpt.
      
display alert “Could not load main.scpt” message errMsg & ” (” & errNum & “)” as critical
    end try
  end applicationWillFinishLaunching_
  
  
on applicationDidFinishLaunching_(aNotification)
    – Insert code here to do startup actions after your application has initialized
    
    
if mainScript is missing value then return
    
    
– Emulate an OSA Applet: Invoke the “run” handler.
    
    
– If we have already opened files during startup, don’t invoke the run handler.
    
if didOpenFiles then return
    
    
try
      tell mainScript to run
    on error errMsg number errNum
      if errNum is not -128 then
        display alert “An error occurred while running” message errMsg & ” (” & errNum & “)” as critical
      end if
    end try
    
    
– TODO: Read the applet’s “stay open” flag and quit if it’s false or unspecified.
    
– For now, all Cocoa Applets stay open and require the run handler to explicitly quit,
    
– which is arguably more correct for a Cocoa application, anyway.
    
(* if not shouldStayOpen then
      quit
    end if *)
  end applicationDidFinishLaunching_
  
  
on applicationShouldHandleReopen_hasVisibleWindows_(sender, flag)
    – Insert code here to perform actions in response to a “reopen” event
    
    
if mainScript is missing value then return true
    
    
– Guard against re-entrancy.
    
if not isReopening then
      set isReopening to true
      
      
– Emulate an OSA Applet: Invoke the “reopen” handler. If there isn’t one, let the application object
      
– handle reopen (this is different from an OSA applet, which would do nothing if there is no handler;
      
– this way, the application will perform the usual “create untitled document” behavior by default).
      
try
        tell mainScript to reopen
        
set isReopening to false
        
        
return false
      on error errMsg number errNum
        if errNum is not -128 then
          display alert “An error occurred while reopening” message errMsg & ” (” & errNum & “)” as critical
        end if
      end try
      
      
set isReopening to false
    end if
    
    
return true
  end applicationShouldHandleReopen_hasVisibleWindows_
  
  
on application_openFiles_(sender, filenames)
    – Insert code here to perform actions in response to an “open documents” event
    
    
– Remember that we opened files, to avoid invoking the “run” handler later.
    
set didOpenFiles to true
    
    
– Guard against re-entrancy.
    
if not isOpeningFiles and mainScript is not missing value then
      set isOpeningFiles to true
      
      
try
        – Convert all the filenames from NSStrings to script strings
        
set theFilenameStrings to {}
        
repeat with eachFile in filenames
          set theFilenameStrings to theFilenameStrings & (eachFile as text)
        end repeat
        
        
tell mainScript to open theFilenameStrings
        
set isOpeningFiles to false
        
        
tell sender to replyToOpenOrPrint_(current application’s NSApplicationDelegateReplySuccess)
      on error errMsg number errNum
        if errNum = -128 then
          tell sender to replyToOpenOrPrint_(current application’s NSApplicationDelegateReplyCancel)
        else
          display alert “An error occurred while opening file(s)” message errMsg & ” (” & errNum & “)” as critical
          
tell sender to replyToOpenOrPrint_(current application’s NSApplicationDelegateReplyFailure)
        end if
      end try
      
      
set isOpeningFiles to false
    else
      tell sender to replyToOpenOrPrint_(current application’s NSApplicationDelegateReplyFailure)
    end if
  end application_openFiles_
  
  
on applicationShouldTerminate_(sender)
    – Insert code here to do any housekeeping before your application quits
    
    
– Guard against re-entrancy.
    
if not isQuitting and mainScript is not missing value then
      set isQuitting to true
      
      
– Emulate an OSA Applet: Invoke the “quit” handler; if the handler returns, it has fully
      
– handled the quit message and we should not quit, otherwise, it calls “continue quit”,
      
– which returns error -10000.
      
try
        tell mainScript to quit
        
set isQuitting to false
        
        
return current application’s NSTerminateCancel
      on error errMsg number errNum
        – -128 means there is no quit handler
        
– -10000 means the handler did “continue quit”
        
if errNum is not -128 and errNum is not -10000 then
          display alert “An error occurred while quitting” message errMsg & ” (” & errNum & “)” as critical
        end if
      end try
      
      
set isQuitting to false
    end if
    
    
return current application’s NSTerminateNow
  end applicationShouldTerminate_
  
end script

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

オリジナルの「おかえり」のコアコードは10行ぐらいで、このサンプルよりもはるかに単純な原理で動いています。
いまから10年近く前にすでに動いていたもので、オリジナルコードはAppleScriptだけで記述。公開版は透明ウィンドウを付けるため(だけ)にAppleScript Studioで開発しました。

「おかえり」のアップデートが止まっていたのは、自分が欲しい機能をてんこ盛りに盛り込んで……画面をInterface Builder上でデザインした時点で「こんなに大量のGUI部品のコードを書くのは大変」なことに気づき……そのまま放置状態に陥ってしまったためでしょう(Piyocastをはじめ、個人的な優先順位の高い自作の未公開アプリがいくつもあるので)。

アプリが自分自身のコード量で自滅した、といえなくもありません。

当時は、Mac AppStoreも存在していませんでしたし、フリーで配布するにはバージョンアップに手がかかりすぎる自作ソフト群にうんざりしていた、ということもあります。

いまだったら、AppleScriptObjCならGUI部品のサポートコードを書くのもそれほど手間ではないので、バージョンアップして公開してみてもよいかもしれません。