ページ

ラベル Launch Services の投稿を表示しています。 すべての投稿を表示
ラベル Launch Services の投稿を表示しています。 すべての投稿を表示

2010年4月16日金曜日

Application List (3) アイコン表示

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: Application List (2) ファイルパスからアプリ名に変換して表示 - LSCopyDisplayNameForURL

前回はアプリ名を表示した。今回はアイコンを表示してみよう。

まずアイコン画像の用意から。ファイルのアイコン画像は -[NSWorkspace iconForFile:] で取得できる。
NSWorkspace Class Reference - iconForFile:

パスから決めることができるので前回同様 -[ApplicationEntry initWithPath:] 内に仕込む。

ApplicationEntry.m

-(id)initWithPath:(NSString*)aPath
{
self = [super init];
if (self != nil) {
self.path = aPath;
   :
   :
self.icon = [[NSWorkspace sharedWorkspace] iconForFile:path];
[icon setSize:NSMakeSize(16, 16)];
}
return self;
}


取得後に表示用のサイズ(16x16)を設定しておく。



次にこの画像を表示する実装。

NSTableView で画像を表示するには NSImageCell を使うのが簡単だが、これでは同じセル内でアプリ名が表示できない。カスタムセルを作って自前で描画する必要がある。この辺り iPhone では標準のセルで簡単にできるので AppKit の方にも用意して欲しい。

自前描画は以前扱ったことがある。
Cocoaの日々: NSTableView にカスタムビューを表示する (6)カスタムセルにモデルオブジェクトの内容を描画する

関連:
Cocoaの日々: NSTableView にカスタムビューを表示する (5)カスタムセルへ bindings経由でモデルオブジェクトを渡す



描画方法について改めてまとめてみる。

1. NSCell のサブクラスを作る(カスタムセルと呼ぶことにする)

2. 表示したい NSTableColumn にカスタムセルのインスタンスを設定する。

3. NSTableColumn の Value に モデルを bindする

4. カスタムセルに2つのメソッドを実装する
-(void) drawInteriorWithFrame:inView:
-(id)copyWithZone:


詳しく見ていこう。





1. NSCell のサブクラスを作る。

ApplicationCell という名のクラスを作る。

@interface ApplicationCell : NSCell {

}

@end


2. 表示したい NSTableColumn にカスタムセルのインスタンスを設定する

今回は AppListAppDelegate に NSTableColumn へのアウトレットを用意し、アプリ起動時に -[NSTableColumn setDataCell:] で設定してやる。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
:
[tableColumn_ setDataCell:
[[[ApplicationCell allocinitautorelease]];
}





3. NSTableColumn の Value に モデルを bindする

今回は ArrayController を介してモデルとつながっている。
AppListAppDelegate.appList ← Array Controller ← NSTableColumn

Interface Builder で NSTableColumn の Bindings設定を開き、Bind to を Array Controller へ、Key は arrangedObjects、Model Key Path を空にしておく。

Model Key Path を空にしておくのがポイントで、これによって ArrayController が扱う ApplicationEntry のインスタンスを直接セルへ渡せる。

なお ArrayController の方はこうなっている。
appList は AppListAppDelegate で定義されている NSMutableArray を指している。


4. カスタムセルに2つのメソッドを実装する

ApplicationCell.m
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
ApplicationEntry* entry = [self objectValue];
[controlView lockFocus];
NSPoint p1 = cellFrame.origin;
p1.x += 0;
p1.y += 0 + [entry.icon size].height;
[entry.icon compositeToPoint:p1 operation:NSCompositeSourceOver];
NSPoint p2 = cellFrame.origin;
p2.x += 20;
p2.y += 0;
NSDictionary* attrs = [NSDictionary dictionary];
[entry.name drawAtPoint:p2 withAttributes:attrs];
[controlView unlockFocus];
}

- (id)copyWithZone:(NSZone *)zone
{
ApplicationCell* cell = (ApplicationCell*)[super copyWithZone:zone];
return cell;
}



-[NSCell objectValue] でArrayController から渡される ApplicationEntry のインスタンスを取得することができる。
copyWithZone: は必須。これが実装さていないと実行時にエラーとなる(NSTableColumn が使う)。



これで、できあがり。

動かしてみよう。
出た。いい感じだ。

ソースコードは GitHub からどうぞ
AppList at 2010-04-15c from xcatsan's SampleCode - GitHub


(参考)セルに画像とテキストを描く情報はHMDT でも紹介されている。参考まで。

この中の「セルに画像とテキストを描く」の箇所。カスタムセルを用意して drawInteriorWithFrame:inView: をオーバーライドすれば良い。


----
次はドラッグ&ドロップによる並び替え。


2010年4月15日木曜日

Application List (2) ファイルパスからアプリ名に変換して表示 - LSCopyDisplayNameForURL

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: Application List (1) 雛形作成

前回はドラッグ&ドロップされたファイルパスのファイル名をそのまま表示していた。


アプリケーションの場合、これをファイル名ではなくアプリケーションの名前にしたい。Launch Service API の LSCopyDisplayNameForURL() を使うと取得できる。
Launch Services Reference - LSCopyDisplayNameForURL


前回のサンプルではモデルクラス ApplicationEntry の nameプロパティを(ArrayControllerを介して) NSTableColumn へbindしていた。ここにアプリ名を入れれば bindingsを通じて表示される。

ApplicationEntry.h

@interface ApplicationEntry : NSObject {

NSString* name;
NSString* path;
NSImage* icon;
}
@property (copy) NSString* name;
@property (copy) NSString* path;
@property (retain) NSImage* icon;
@end

path から name を決められる(後日やる iconも同じ)ので pathを引数に取るイニシャライザを追加する。

ApplicationEntry.m

-(id)initWithPath:(NSString*)aPath
{
self = [super init];
if (self != nil) {
self.path = aPath;
NSString* appName;
LSCopyDisplayNameForURL((CFURLRef)[NSURL fileURLWithPath:path], (CFStringRef *)&appName);
if (!appName) {
appName = @"(not found)";
}
self.name = appName;
}
return self;
}


ドロップ処理でこのイニシャライザを使う。
AppListAppDelegate.m

- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info
  row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
{
NSPasteboard* pboard = [info draggingPasteboard];
NSArray*filenames = [pboard propertyListForType:NSFilenamesPboardType];

for (NSString* filename in filenames) {
ApplicationEntry* entry = [[[ApplicationEntry alloc] initWithPath:filename] autorelease];
[arrayController_ insertObject:entry atArrangedObjectIndex:row];
}

return YES;
}



動かしてみる。
出た。

2009年11月11日水曜日

ログイン時にアプリケーションを自動的に起動する設定を行う(LaunchService/Shared File Lists を使う)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

アプリケーションをログイン時に自動起動するようにしたい。ユーザ自身が手動で行うのではなく、プログラム側で制御するにはどうやったら良いのか。

手動で行う場合

システム環境設定のアカウント-ログイン項目を開きアプリを指定すれば次回ログイン時から有効となる。



LaunchService - Shared File Lists

ネットで調べたところいくつか方法があることがわかった。

まず Mac Dev Center より。
Mac Dev Center: System Startup Programming Topics: Customizing Login and Logout

いくつか方法があるようだ。
  • LauchService の Shared File Lists を使う(Mac OS X v10.5以上)
  • Apple Events を使う(MacOSX v10.5以前)
  • CFPreferences を使う

他の情報サイトでも取り上げられていた。

How do you make your App open at login? - Stack Overflow

Shared File Lists を使ったサンプルコードが紹介されている(ただし追加のみ)。


Automatically start applications on login - Mac Forums

こちらでも Shared File Lists を使ったコードが紹介されていた(追加および解除両方)。


Shared File Lists を使う方法が簡単でわかりやすい。これを試してみよう。


サンプルコード

ログイン項目への追加、除去を行うだけのサンプルプログラムを作ってみた。

チェックをつけるとこのサンプルプログラム自身をログイン項目へ追加し、チェックを外すと除去する。

ソースコードは、github に上げたので詳細はそちらを見てほしい。
AutostartOnLogin at master from xcatsan's SampleCode - GitHub

メインとなるログイン項目を追加するコードは先のサイトからコピー&ペーストしただけ。LSSharedFileList* 系の関数(LaunchService)を使い LSSharedFileListItem の Insert/Remove を行っているようだ。

実行してみよう。初期状態ではログイン項目には追加されていない。


チェックを付けて見る。

ログイン項目に追加された(一番下の AutostartOnLogin)。


チェックを外すとログイン項目から除去された。


チェックを付けてログイン項目へ追加した上で、試しに一旦ログアウトして再ログインしてみよう。


起動した。

- - - - -
こんなに簡単にできるとは思わなかった。後で SimpleCapへ組み込んでおくことにする。


(サンプルは 10.6 で開発、実行した)


2008年11月3日月曜日

アプリケーションを開く(6)ソートする

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

アプリの並びをソートする。ソートはアプリケーションの URLではなく名前で行う。現状名前のリストは持っていない。そこでアプリのURL、名前、アイコンを保持する小さなモデルクラスを用意し、ここに compare: メソッドを実装することにした。

AppEntry.h

@interface AppEntry : NSObject
{
NSURL* _url;
NSString* _name;
NSImage* _image;
}
@property (retain) NSURL* url;
@property (retain) NSString* name;
@property (retain) NSImage* image;


#compre: は nameで比較すれば良い。
- (NSComparisonResult)compare:(AppEntry*)entry
{
return [_name compare:entry.name];
}


その上で Launch Services API から取得したアプリ一覧を元に一旦 AppEntryのインスタンスを作り、配列へ入れておく。そしてその配列へ sortedArrayUsingSelector: を投げてやる。
NSArray* sorted_entry_list = [entry_list sortedArrayUsingSelector:@selector(compare:)];


できた。


サンプル:FindingAllApps-4.zip

2008年11月2日日曜日

アプリケーションを開く(5)デフォルトアプリ表示

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

次にデフォルトアプリケーションを一番上にくるようにする。デフォルトアプリは Launch Services API の LSGetApplicationForURL が使える。

こんな感じ。

 FSRef outAppRef;
NSURL* default_url;
LSGetApplicationForURL((CFURLRef)target_url, kLSRolesAll, &outAppRef, (CFURLRef*)&default_url);



セパレータ(NSMenuItem#separatorItem)も入れてできあがり。


サンプル:FindingAllApps-3.zip

- - - -
ファインダの「このアプリケーションで開く」では、デフォルトアプリに「(デフォルト)」と入れたり、同じアプリケーション名称が存在する場合(図の Firefox)はバージョン番号を加えたりとなかなか芸が細かい。

2008年11月1日土曜日

アプリケーションを開く(4)アプリの一覧表示

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

さて利用可能なアプリケーションの一覧を取得する方法がわかったので今度はこれを NSPopUpButtonで表示してみる。
Cocoa Bindings を使いたいところだが、今回はアイコン画像を表示したいので普通にコーディングすることにした(*)。

サンプル:FindingAllApps-2.zip

バンドル内に用意した dummy.jpg を扱えるアプリの一覧がアイコン付きで表示される。


コードはこんな感じ。主要な検証は前回までで済んでいるので、NSPopUpButtonとNSMenuのセットアップが中心。

- (NSArray*)menuItems
{
NSString* path = [[NSBundle mainBundle] pathForImageResource:@"dummy.jpg"];
NSURL* target_url = [NSURL fileURLWithPath:path];
NSArray* app_list = [(NSArray*)LSCopyApplicationURLsForURL((CFURLRef)target_url, kLSRolesAll) autorelease];

NSMutableArray* item_list = [NSMutableArray array];
NSString* display_name;
NSImage * image;

for (NSURL* url in app_list) {
LSCopyDisplayNameForURL((CFURLRef)url, (CFStringRef *)&display_name);
image = [[NSWorkspace sharedWorkspace] iconForFile:[url path]];
[image setSize:NSMakeSize(16, 16)];

NSMenuItem* item = [[[NSMenuItem alloc] init] autorelease];
[item setTitle:display_name];
[item setImage:image];
[item setTarget:self];
[item setAction:@selector(selectApplication:)];
[item setRepresentedObject:
[NSDictionary dictionaryWithObjectsAndKeys:url, @"url", nil]];
[item_list addObject:item];
[display_name release];
}

return item_list;
}



(*) NSPopUpButton+CocoaBindings で画像を表示する。
CocoaBindingsで画像が扱えないことに不満を持った人が他にも居たみたいで、poseAsClass で無理くり?対応させた強者がいた。
Cocoatech: Bindings and NSPopUpButton hack
なるほど。面白い(まねしないけど)。
掲載された画像を見るとどうも同じこと(あるファイルを扱うことのできるアプリ一覧)をやっているようだ。
(ああ PathFinderの作者のページか!)

2008年10月31日金曜日

アプリケーションを開く(3)拡張子から利用可能なアプリを調べる?

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

前回は Launch Services API の LSCopyApplicationURLsForURLを使ってオープン可能なアプリケーションの一覧を取得した。
例えば pro.jpg というファイルの場合、次のリストが取得できた。

    file://localhost/Applications/QuickTime%20Player.app/,
file://localhost/Developer/Applications/Xcode.app/,
file://localhost/Applications/Gimp.app/,
file://localhost/Applications/OpenOffice.org%202.1.app/,
file://localhost/Applications/Safari.app/,
file://localhost/Applications/Preview.app/,
file://localhost/Developer/Applications/Graphics%20Tools/Core%20Image%20Fun%20House.app/,
file://localhost/Applications/Adobe%20Illustrator%20CS2/Adobe%20Illustrator.app/,
file://localhost/Applications/%E3%83%84%E3%83%BC%E3%83%AB/Firefox.2.app/,
file://localhost/Applications/tools/Firefox.app/,
file://localhost/Applications/Utilities/ColorSync%20Utility.app/,
file://localhost/Users/hashi/development/study/package/build/Release/package.app/,
file://localhost/Applications/Firefox.app/


SimpleCapで扱うファイルは GIF/JPEG/PNG なので、特定のファイルを開けるアプリの一覧よりは、これらの種類を開けるアプリの一覧が欲しい。API の中に LSCopyAllRoleHandlersForContentType があったのでこれを使ってみる。結果は下記の通り。
    "com.apple.CoreImageFunHouse.app",
"org.gimp.Gimp",
"com.apple.Preview",
"com.apple.ColorSyncUtility"


むむ。返される形式が異なるのは別として、アプリの数が少ない。

これは結局2つの関数の検索条件が異なることに起因しているようだ。前者は拡張子かクリエータを対象に、後者は Info.plistのLSCopyDefaultRoleHandlerForContentTypeエントリを対象として検索している。

この話題はメーリングリストでも取り上げられていた。
Re: Get a list of all Apps for a File


なるほど。

だったらダミーファイルを GIF/JPEG/PNGの3種類、バンドル内に用意してこれを使って対応アプリを取得することにしよう。

かなり安易だが。。

#ちなみに試しに実在しないファイルを LSCopyApplicationURLsForURL を渡したところ nullが返ってきた。

2008年10月30日木曜日

アプリケーションを開く(2)「このアプリ..で開く」

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

プレビューは開くことができたので、こんどは起動するアプリケーションを環境設定で自由に選べるようにする。

イメージはファインダの「このアプリケーションで開く」


さてこれを実現するにはファイルを開くのに適したアプリケーションを取得する必要がある。デフォルトのアプリは NSWorkspace#getInfoForFile:application:type: で取れそうだが、オープン可能なアプリ一覧となると NSWorkspace では役不足のようだ。

MacOSXには Launch Services という API が用意されていれていて、このあたりを扱うことができる。

Launch Services Programming Guide

このAPIの中に LSCopyApplicationURLsForURL という関数が用意されていて、これが使えそうだ。

(参考)Re: Get list of possible applications


早速サンプルアプリを作って試してみる。

サンプル:FindingAllApps-1.zip

実行するとソースに付随している画像ファイル pro.jpg を開くことができるアプリの一覧がコンソールへ出力される。


コードはこんな感じ。

 NSString* path = [[NSBundle mainBundle] pathForImageResource:@"pro"];
NSURL* url = [NSURL fileURLWithPath:path];
NSArray* array = [(NSArray*)LSCopyApplicationURLsForURL((CFURLRef)url, kLSRolesAll) autorelease];
NSLog(@"%@", array);


実行結果。