2011年2月28日月曜日

Cocoa ObjC 環境設定パネル

環境設定パネルを作ろうとして・・・あんまり設定する項目が思いつかないなあ。(^^;)

色とか透明度とか・・・そんなのたいした必要とも思えないし。

Workspaceを切り替えるということは、起動中のアプリを切り替えたいわけだから、タスクスイッチャは表示できるようにしようと考えている。そうすると、タスクスイッチャの有効化、それと表示形式(アイコンのみ、アイコン+テキスト、テキストのみ、くらい)が設定項目か。

もともとメインのビューはsubview(この場合、Workspace切替ViewとタスクスイッチャView)の表示の管理をするためのビューなので、複数のsubviewを等間隔で表示するようにはコーディングしてある。だからほんのちょっとのコードの追加で以下のような表示にできた。


右側の領域にタスクスイッチャを作っていく。

というわけで設定パネル。普通に作ればこんな感じになる。


superclassはNSPanelにしてあるが、これでも起動時は他のアプリのウィンドウに隠れてしまうことがある。これをHUDのウィンドウにすると、


うーむ、いつでも最前面に表示できるようになるものの、いささかカッコ悪いかも。TabViewが。HUDに特化したようなInterfaceBuilder用pluginがあったはず、とInstapaperを見返してみたら、あったあった、いつぞやxcatsan師匠のblogのコメント欄で紹介されていた

BWToolkit

ここからダウンロードできるフレームワークを使えば、こんな感じのコントロールが使えるようになるらしい。


あとは環境設定パネルのためだけに、この種のフレームワークを使うのがいいかどうか、ですな。確実にアプリのサイズは大きくなるだろうし。

TabViewを使うにしてもToolBarを併用すればこんな風な表示になる。

Toolbarでtabを切り替えるようにすれば、見た目としてはまあまあか。でもチェックボックスとかはCocoaインターフェイスのままだから、結局HUDとは相性がよくないので、やはりBWToolKitを使うかどうかだなあ。

超軽量ツールなんだから、変にこだわらないほうがいい気がする。

2011年2月27日日曜日

趣味は料理

っても毎日の夕食作りを担当しているので、趣味ではないか。(^^;)

共働きなので家事は当然です。
小学生くらいから台所で母親の料理しているところにまとわりついていたので、「食べることへの興味と意欲と関心」が高くて、それがそのまま料理することへの関心につながったと思う。いわゆるミートソースなんかは高校生の時から研究して、このごろやっと完成の域に達した。さらに最近、さるイタリア料理のレシピ本を借りたら、「こくが足りないときはドライトマトのオイル漬けをみじん切りにして1個分いれよ」という記述があって驚愕。なるほどなあ。今度やってみよう。

で、本日図書館でこんな本を借りた。

はじめてのラーメン店オープンBOOK (お店やろうよ! (19))
バウンド
技術評論社
売り上げランキング: 158645

いやべつにラーメン店をやりたいわけではなく(・・・いや、もしやってもいいならやってみたいか)、ラーメン作りに対する興味と、他の職業に対する関心を十分満たしてくれそうだ、ということで。

結論から言うとこの本めちゃくちゃ面白いです、はい。豚骨ラーメンつくりてえー。

Cocoa Warpのソースを読む(2)

Spaces.appがらみのToolであるWarpのソースを読む。もう少し付加機能をつけようと考えていて、そのための勉強。

ソースを読むと実際の動作よりかなりいろいろな機能がつけられている。
Warpの主要な機能は、スクリーンのはしっこにマウスカーソルを近づけるととなりのWorkspaceのプレビューが出て、それをクリックするとWorkspaceが切り替わる。プレビューはこんな感じ。


ソースを読むと、このプレビューはサイズ変更ができるみたいなんだけど実際にはできない。将来のバージョンアップのための準備なのかしらん。

で、そのサイズ変更の部分のソースには、直前のアーティクルの

CocoaDev/BorderlessWindow
このソースがそのまま使われていた。orz、答えはすぐそばにあったのか。なんてこった。

オープンソースのプロジェクトのソースってたいてい私には解読不可能な場合が多いんだけど(特にC++はもう本当にわかんない)、ObjCだと比較的わかりやすい。読みやすいソースが書ける、ということなのかな。

Cocoa ObjC 透明ウィンドウのリサイズ

昨日GiveUpしたのに、本日実現できた、透明ウィンドウのサイズ変更。そんなものか。あきらめたらそこで試合終了、になるところだった。

さて、昨夜あれこれググった果てにこんなフォーラムに行きあたった。

CocoaDev/BorderlessWindow

この記事の中盤以降のソースが透明ウィンドウのリサイズに関する話になっていて、なんとなくこれが正解になりそうと当たりをつける。

本日実際に試してみたら見事にBingo!該当のソースを引用すると
- (void)mouseDragged:(NSEvent *)theEvent
{
 if (shouldRedoInitials)
 {
  initialLocation = [theEvent locationInWindow];
  initialLocationOnScreen = [self convertBaseToScreen:[theEvent locationInWindow]];
  
  initialFrame = [self frame];
  shouldRedoInitials = NO;
  
  if (initialLocation.x > initialFrame.size.width - 20 && initialLocation.y < 20) {
   shouldDrag = NO;
  }
  else {
   //mouseDownType = PALMOUSEDRAGSHOULDMOVE;
   shouldDrag = YES;
  }
  
  screenFrame = [[NSScreen mainScreen] frame];
  windowFrame = [self frame];
  
  minY = windowFrame.origin.y+(windowFrame.size.height-288);
 }
 
 
 // 1. Is the Event a resize drag (test for bottom right-hand corner)?
 if (shouldDrag == FALSE)
 {
  // i. Remember the current downpoint
  NSPoint currentLocationOnScreen = [self convertBaseToScreen:[self mouseLocationOutsideOfEventStream]];
  currentLocation = [theEvent locationInWindow];
  
  // ii. Adjust the frame size accordingly
  float heightDelta = (currentLocationOnScreen.y - initialLocationOnScreen.y);
  
  if ((initialFrame.size.height - heightDelta) < 289)
  {
   windowFrame.size.height = 288;
   //windowFrame.origin.y = initialLocation.y-(initialLocation.y - windowFrame.origin.y)+heightDelta;
   windowFrame.origin.y = minY;
  } else
  {
   windowFrame.size.height = (initialFrame.size.height - heightDelta);
   windowFrame.origin.y = (initialFrame.origin.y + heightDelta);
  }
  
  windowFrame.size.width = initialFrame.size.width + (currentLocation.x - initialLocation.x);
  if (windowFrame.size.width < 323)
  {
   windowFrame.size.width = 323;
  }
  
  // iii. Set
  [self setFrame:windowFrame display:YES animate:NO];
 }
    else
 {
  //grab the current global mouse location; we could just as easily get the mouse location 
  //in the same way as we do in -mouseDown:
  currentLocation = [self convertBaseToScreen:[self mouseLocationOutsideOfEventStream]];
  newOrigin.x = currentLocation.x - initialLocation.x;
  newOrigin.y = currentLocation.y - initialLocation.y;
  
  // Don't let window get dragged up under the menu bar
  if( (newOrigin.y+windowFrame.size.height) > (screenFrame.origin.y+screenFrame.size.height) )
  {
   newOrigin.y=screenFrame.origin.y + (screenFrame.size.height-windowFrame.size.height);
  }
  
  //go ahead and move the window to the new location
  [self setFrameOrigin:newOrigin];
  
 }
}

- (void)mouseUp:(NSEvent *)theEvent
{
 shouldRedoInitials = YES;
}

これをカスタムクラス化したNSWindowに組み込めばそれでOK。ちなみにヘッダには
    BOOL shouldDrag;
 BOOL shouldRedoInitials;
 NSPoint initialLocation;
 NSPoint initialLocationOnScreen;
 NSRect initialFrame;
 NSPoint currentLocation;
 NSPoint newOrigin;
 NSRect screenFrame;
 NSRect windowFrame;
 float minY; 

以上の変数を加えておく。Windowクラスの初期化時に

shouldRedoInitials = YES;

を書いておかないと、マウスのファーストタッチで変な場所にウィンドウが飛ばされる、ということだけ、自分であれこれ試行錯誤して確かめた。
透明ウィンドウなのでマウスのイベント拾わないはずなんだけど、なぜかなにもしないでもイベントを拾うな。
というわけで何日間かのもやもやが晴れてすっきり。自分で考え出したことではないので一点、曇がある感じもするけど(ぜーたく)

今度は環境設定を実装していこう。

2011年2月26日土曜日

Cocoa ObjC Viewだけでリサイズ GiveUp

結論から言うと現在のところでは「透明ウィンドウ」のリサイズをView側からなんとかしようというのは、私の知識と技術では無理、と判断した。がっくり。

Viewにリサイズ用のアンカーをつけて、と考えていた。ちょうどDashBoardのいくつかのウィジェットのように。


しかしframeとbounds、Viewの座標系とWindowの座標系とscreen座標系の関係、などなど、知らないことが多すぎてにっちもさっちもいかない感じ。

もう面倒なので、サービスメニューから「ウィンドウ枠を表示する」みたいな選択肢を用意して、それでリサイズ可能、ということにしてしまおう。

あと、BackGroundOnlyで動作させればタイトルバーとリサイズ用のアンカーが表示してあってもそれほど気にならないので、せめてタイトルバーの高さをもう少し低くというか狭くできたらかっこいいかな、と考えてあれこれ調べ始める。

タイトルバーのカスタマイズはstack overflowあたりでもけっこうな質問が上がっていて、みんな考えることは一緒か。で、一般的な結論が

1,透明ウィンドウにして
2,独自のタイトルバーをつけよ(NSViewのカスタムクラスにして)

ってそれじゃあリサイズができんだろうが!

もう少し「NSBorderlessWindowMask resize」とかでググってみる必要があるなあ。
タイトルバーのカスタマイズを考えるにあたって、いくつかのアプリのタイトルバーを調べたら、やっぱApple謹製のアプリはやりたい放題であることがわかった。

純正Twitterアプリ。タイトルバーがウィンドウの左側にはりついている?クローズボタンとかもカスタマイズされているなあ。


MacのAppStore。タイトルバーがない・・・。


そしてGoogleのChrome。

Chormeのtabバーの再実装はすでにいくつかあるようで、身近なところではいつぞや紹介したKodというエディタ用に
chromium-tabs

といったプロジェクトがあったりする。もう少し調査してみよう。

2011年2月25日金曜日

Cocoa ObjC Viewだけでリサイズ(3)

今度は「デスクトップいっぱいの透明ウィンドウ」の中で作業してみる。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
 // Insert code here to initialize your application 
 NSScreen* main_screen = [NSScreen mainScreen];
 NSRect fullscreen_frame = [main_screen frame];
 [window setFrame:fullscreen_frame display:YES];
 [window setStyleMask:NSResizableWindowMask];
 [window setOpaque:NO];
 [window setBackgroundColor:[NSColor clearColor]];
 [window makeKeyAndOrderFront:self];
 NSRect rect=NSMakeRect(100, 100, 640, 480);
 MovableView *view=[[MovableView alloc] initWithFrame:rect];
 [[window contentView] addSubview:view];
}

今回はMovableViewというNSViewのサブクラスを動的に生成してみた。

このViewをマウスのドラッグで動かしたり、リサイズしたりする実験。

本日はドラッグでViewだけ動かしておしまい。
簡単に考えると、マウスのドラッグの移動量を現在のFrameのrectに足して、改めて[self setFrame:rect]とかやればいいような気がするわけだが、これがなぜかうまくいかない。rectのoriginが想定よりも倍くらい増えるんだなあ。なぜだろう。

マウスドラッグを追いかける方法はデベロッパドキュメントには3通り載っていて、xcatsan師匠はmouseDownでloopを回す方法をとっているけれど、今回は最初の例、mouseDownでドラッグ中のflagを立て、mouseDraggedで実際の処理をしてみた。

-(void)mouseDown:(NSEvent *)event{
 NSLog(@"mouseDown");
 NSPoint p = [self convertPoint:[event locationInWindow] fromView:nil];
 if (NSPointInRect(p,[self frame])){
  windowDrag=YES;
        }
}
-(void)mouseDragged:(NSEvent *)theEvent{
 NSLog(@"drag!!");
 if(windowDrag){
  NSRect rect=[self frame];
  [self translateOriginToPoint:NSMakePoint(theEvent.deltaX, -theEvent.deltaY)];
  [self setFrameSize:NSMakeSize(rect.size.width+theEvent.deltaX, rect.size.height-theEvent.deltaY)];
  [self setNeedsDisplay:YES];
    NSLog(@"df:%f dy %f",theEvent.deltaX,theEvent.deltaY);
 
 }  
}
とりあえず以上で、View内でマウスをクリック→ドラッグしたらViewが動く、という処理が実現できている。[self translateOriginToPoint:NSMakePoint(theEvent.deltaX, -theEvent.deltaY)];としないと、なぜoriginが想定したように動かないのかまだ理解出来ていない。結果オーライなコードなので自分でもかなり信用度が低い。(^^;)

明日は休みなのでもう少し実験できるかな。
しかし在宅仕事の必要があるのでどの程度時間を割けるか・・・。

2011年2月24日木曜日

iPad2は3月2日ですか

iPad用にとっておいたへそくりが、プログラミング関連の書籍購入のためけっこう減ってしまった。(^^;)

足りない分をお母ちゃんが出してくれるかどうか・・・ちょっと心配。

週日は疲れていて、iMacの前に座っているのがつらくなることがある。そういう時にベッドの中でiPadであれこれ、がいいかも。

プログラミングという点では、デザイナ的才能が皆無に近いのでiOSにはきっと手が出ないと思われる。見た目のしょぼいiOSアプリ、自分だって使おうとおもわんもんな。

MacRuby0.9

0.9は明日25日金曜日に公開らしい。

ObjCをある程度勉強したら、またMacRubyに戻ってみたい。ObjCもかなり手軽にあれこれできる分、MacRubyはさらにその上をいく感じなので。私のような休日趣味プログラマにはちょうどいい。

Cocoa ObjC Viewだけでリサイズ(2)

本日もViewのリサイズの研究。端的に言ってうまくいかない。知識が少なすぎるのが主な原因。orz

xcatsan師匠「SimpleCap RubberBand」シリーズを参考にしていろいろ実験。

xcatsan師匠と同じように、View中に四角い図形を描く、という手法ならうまくいく。その図形の大きさにViewのframeを合わせようとすると今の自分には手に負えない。こんなふうになってしまう。


もともとのViewの描画が残ってしまうみたい。

うーん、ま、もう少し試してみるけれど、簡単な解決法は

1,リサイズ時はWindowのFrameを表示するようにする
2,Windowをデスクトップと同じ大きさにしてしまって後は気にしないことにする(^^;)・・それでもViewのFrameの問題は残る
3,MainViewもデスクトップと同じ大きさで完全に透明にしてしまい、あとは気にしないことにする(^^;)

MainViewは現在nibに入っているので、これを動的に生成するようにすれば2で済むか。うーむ。安易な方法を選ぶのはやめると決めているので、最低で1だなあ。

ここらでもっとWindowのframeとViewのFrame、boundsの関係とかを根本的にわかるまで勉強するべきだ、と自戒する。

2011年2月23日水曜日

Cocoa ObjC Viewだけでリサイズ

透明ウィンドウで、Viewだけの状態でウィンドウごとリサイズできるか調査中。

昨日作った実験用プロジェクトで、あれこれ実験。まだ成果はなし。どーもViewのsetFrameがうまくいかない。

要は、Viewのサイズをかえたらウィンドウのサイズも連動してかえる、ということ。普通はウィンドウのサイズ変更がViewの大きさと連動するから、その逆。

簡単な方法としては、メニューかなにかでウィンドウを可視化してしまう、というのがあるわけだが、ここはやはりViewだけが見える状態でウィンドウごとリサイズできないかやってみようとしている。


ウィンドウは透明化して、リサイズのアンカーとタイトルバーだけが表示されいる状態。そこに半透明なViewを表示して、Viewの右隅にNSTrackingAreaを作っている。白い四角がtrackingarea。

trackingareaでマウスを左クリックするとサイズ変更、とやりたいわけだが、どーも納得いく動きにならない。なぜだろうかなあ。さらに明日以降研究する。

2011年2月22日火曜日

Cocoa ObjC NSGradient

NSBezierPathの中身をグラディーションで描画してみる。

まずはxcatsan師匠教えの通りにやってみる。

実験用プロジェクトをつくり、Viewに単純な四角を黒でぬりつぶす。

NSRect rect=NSMakeRect(100, 100, 100, 200);
NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:2 yRadius:2];
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:1.0] set];
[path fill];
これで次のようになる。

この四角をグラデーションで塗りつぶす。

NSArray* colorArray = [NSArray arrayWithObjects:
         [NSColor colorWithDeviceWhite:1.0 alpha:1.0],
         [NSColor colorWithDeviceWhite:0.8 alpha:1.0],
         [NSColor colorWithDeviceWhite:0.4 alpha:1.0],
         [NSColor colorWithDeviceWhite:0.1 alpha:1.0],
         [NSColor colorWithDeviceWhite:0.0 alpha:1.0],
         nil];
NSGradient* gradient = [[NSGradient alloc] initWithColors:colorArray];
[gradient drawInBezierPath:path angle:270];



これは5階調もつけてしまった例。こんなにたくさん階調をつけなくてもそれらしくなる、ということが実験でわかったので、あとあと3階調に減らしている。しかし、簡単だなあ。NSColorの配列を渡してinitするだけでいいのか。drawInBezierPathしているけど、drawInRectの方が単純でいいのかな。

5階調のまま半透明なウィンドウ&Viewの上に描画してみる。


これを3階調に減らしてみる。


なるほど、これならすぐにSpinに組み込んでも大丈夫だろうということで、SpinのSpacesViewの描画部分に入れてみる。


現在activeなWorkspaceだけalphaが1.0でくどいので、alphaを落としてみる。


さらにグレイから黒へのグラデーションでに変更。


Spinへの組み込みはこんな感じのソースにしてある。
-(NSArray *)setColorArray:(BOOL)state{
 float alha;
 
 if (state==YES) alha=0.8;
 else alha=0.5;
  
  NSArray* colorArray = [NSArray arrayWithObjects:
          [NSColor colorWithDeviceWhite:0.4 alpha:alha],
          [NSColor colorWithDeviceWhite:0.1 alpha:alha],
          [NSColor colorWithDeviceWhite:0.0 alpha:alha],
          nil];
 return colorArray;
 
}
- (void)drawRect:(NSRect)dirtyRect {
NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] xRadius:5 yRadius:5];
 NSArray* colorArray;
 if ([self active]==YES) {
  colorArray = [self setColorArray:YES];  
 }
 else{
  colorArray = [self setColorArray:NO]; 
 }
 
 NSGradient* gradient = [[NSGradient alloc] initWithColors:colorArray];
 [gradient drawInBezierPath:path angle:270];
}
見て目についてはもう少し研究したい。

2011年2月21日月曜日

Cocoa ObjC 試行錯誤のスタイル

xcatsan師匠のblogを読んでいて、とにかく参考になったのが「メインのプロジェクト」に取り込む機能をつくるため、試験プロジェクトをたくさん作る、というスタイル。

バージョン管理システムを使うのはいいとして、メインのプロジェクトにあれこれ書き散らかすと結局なにがなんだかわからなくなるので、試験的なプロジェクトを作ったほうがいい、とはわかっていたが、今まではなんとなくあれこれ書き散らかしていた。

cocoaの場合、試験的なプロジェクトを作ってちょっとした機能を試してみる、という作業が非常に楽にできるみたい。

グラディーションの実験も、新しく試験プロジェクトをひとつ作ったほうがコードの見通しもいいだろうと思っている。

OSXに乗り換えてよかったなー、とこのごろ強く思う。

Cocoa ObjC Spin(その3) removeFromSuperview

Spacesの環境設定が変わったとき、どのようにSpinの表示も変えるか、という話。Notificationがらみの部分は後日。(自分用の備忘録という性格が強いblogなので、誰も知りたくないようなメモが続く(^^;))

とりあえずSpacesの設定が変わったというNotificationを受け取ったら、Spacesの「行と桁」を取得しなおして、それに合わせてsubviewの数も変える。増えただけなら単純に増やしてaddSubviewする。

減った時は、「行と桁」をかけて最大Workspace数を得、「現在のsubviewの数」と比べて減らす。

NSArray *views=[self subviews];

if([views count]>0){
  if(spacesMax > [views count]){
   for(NSInteger i=[views count];i<spacesMax;i++){
    SpacesView *spview=[[SpacesView alloc] initWithFrame:rect spaceNo:i+1];
    //NSLog(@"add View:no %d",[spview spaceNo]);
    [self addSubview:spview];
    [spview release];
   }
  }else if(spacesMax < [views count]) {
   for(NSInteger i=[views count]-1;i>=spacesMax;i--){
    SpacesView *spview=[views objectAtIndex:i];
    //NSLog(@"remove View:no %d count:%d MAX:%d loop:i:%d",[spview spaceNo],[views count],spacesMax,i);
    [spview removeSelf];
   }
  }
  CGSWorkspace currentSpace=[self spaceNumber];
  for(NSInteger i=0;i<spacesMax;i++){
   if (i==currentSpace-1) {
    SpacesView *spview=[views objectAtIndex:i];
    [spview setActive:YES];
   }
  }
    
 }

相変わらず半角<がbloggerに怒られるので全角にしてある。うーん、下手なコードだなー。もう少しきれいに書けるなあ。似たような処理をまとめて他のメソッドにしてしまおう、明日あたり。setActiveは「現在のアクティブなWorkspace」を表すBOOL値。YESなら自分がアクティブ、ということで。

多少見た目もいじって現在はこんな感じ。

removeFromSuperviewは実際は「SpacesView」というクラス(subviewになってるクラス)のメソッドで記述している。removeするのをindexの大きい方から(つまり配列の後ろから)やっているので、MutableArrayの作り直しが発生していないようだ。ま、順調に動いている。Spacesの設定をいじってもほぼリアルタイムに反映できるようになった。


次は見た目。グラディーションを使えば少しは見映えがするはず。そう思って「cocoa グラディーション」とググったら、xcatsan師匠新cocoの最近の記事に行き着いた。なるほどー、こうやるのか。

あと、透明ウィンドウでの、半透明Viewを使ったリサイズ。まだまだ調べることがたくさんある。楽しいなあ。

2011年2月20日日曜日

OSX 使っているアプリをさらす

最近、XcodeとInterfaceBuilderとiTermとSimpleCapとFirefoxしか使っていない、と感じる。

OSXを使い始めて日が浅いので、どんなアプリをどんなふうに使うのかよくわかっていないと自分では思う。「ばりばりのOSX使い」の人たちはどんなアプリをつかってるんだろう。

現在、私のDockはこんな感じ。


もともと、WinでもUbuntuでもデスクトップ上にあれこれアイコンを置くのはきらい。Dockのような「ランチャにしてタスクスイッチャ」にしても、できるだけシンプルな表示になるよう心がけている。ブラウザが3つ並んでいるのはrails触っていた名残ですな。最近はEmacsも全然起動していないや。

さらにApplicationはこうなる。


アイコンから起動することの少ないアプリはappやdevというフォルダに収めている。これでもたくさんありすぎるかな、と思うんだけどどうなのかなあ。

合理的で使いやすいDockやApplicationの整理の仕方を解説したサイトはないものか・・。今度OSXの入門書を図書館で借りてみよう(^^;)。

Cocoa ObjC Spin(その2)

超小物アプリなので、大枠は本日できあがった。さんざん書き散らかした実験用コードのカットアンドペーストが多かったのですぐに基本的な部分はできあがったが・・・。

とりあえずの実行画面はこんな感じ。


現在アクティブなWorkspaceだけ色を変えて表示してみた。これをスクリーンのどこかにおいてマウスをころころするとなかなかいい具合。


Workspace1つにつき、SpacesViewと名付けたViewを1つ割り振っている。それをaddSubviewしている、と。Viewなのでイベント処理とかが楽にできるだろう、という目算。

現在のところの問題点は

Spacesの設定が変わったあとの処理

これはWarpのソースを読みながら該当する箇所をカットアンドペーストして、エラーがでないように修正して、という感じでけっこうさくさく実装できた。しかし、

a,設定が変わったら、いったん全部のsubviewを「remveFormSuperview」で削除する
b,それから新しく「Workspaceの総数」分のsubviewをaddする

という手順をとったところ、aの段階でものすごい時間がかかることが判明した。コンソール出力はこんな感じになる。

2011-02-20 21:15:36.032 Spin[7077:a0f] *** Collection was mutated while being enumerated.CFArray 0x100121540 [0x7fff70429ee0]{type = mutable-small, count = 7, values = (
0 : SpacesView: 0x1001276e0
1 : SpacesView: 0x102a13a10
2 : SpacesView: 0x102a13680
3 : SpacesView: 0x102a13ac0
4 : SpacesView: 0x10050d320
5 : SpacesView: 0x102700090
6 : SpacesView: 0x102a13e50
)}
2011-02-20 21:15:36.034 Spin[7077:a0f] Call ConfigChange!
2011-02-20 21:15:36.034 Spin[7077:a0f] remove No2
2011-02-20 21:15:36.035 Spin[7077:a0f] *** Collection was mutated while being enumerated.CFArray 0x100121540 [0x7fff70429ee0]{type = mutable-small, count = 6, values = (
0 : SpacesView: 0x102a13a10
1 : SpacesView: 0x102a13680
2 : SpacesView: 0x102a13ac0
3 : SpacesView: 0x10050d320
4 : SpacesView: 0x102700090
5 : SpacesView: 0x102a13e50
)}
2011-02-20 21:15:37.911 Spin[7077:a0f] Call ConfigChange!
2011-02-20 21:15:37.911 Spin[7077:a0f] remove No3
2011-02-20 21:15:37.911 Spin[7077:a0f] *** Collection was mutated while being enumerated.CFArray 0x100121540 [0x7fff70429ee0]{type = mutable-small, count = 5, values = (
0 : SpacesView: 0x102a13680
1 : SpacesView: 0x102a13ac0
2 : SpacesView: 0x10050d320
3 : SpacesView: 0x102700090
4 : SpacesView: 0x102a13e50
)}
(tagとみなされるので、引用中の半角<>は全部削除してあります)
1つのsubviewをremoveFromSuperviewするたびに、viewを収めていたMutableArrayが切り詰められている様子。ひとつ切り詰めるたびに1秒近くかかっている。

変だなあ、なにかおかしなことしているのかなあ。
時間はかかるけど、処理そのものはエラーがでるわけでもなく、時間がかかったあとは正常に動作する。こういうのがいちばん厄介だ。
一応明日以降の方針としては、「Workspaceが増えた場合は単純にsubviewを増やすだけ」、「Workspaceが減った場合は、減った分のsubviewだけ削除してあとは残す」という感じで実装してみる。Spacesの設定変更は滅多に行われないけれど、こういう部分がやはりプログラミングの肝、と自分では考える。ここらをていねいに作ってこそ、プログラミングの勉強になるんだな。

見た目(^^;)
すてきな見た目を作るにはどーしたらいいんだろう。現在ののっぺりした感じをもっとかっこよくするにはどうしたらいいのか研究する。

ウィンドウのサイズ変更
現在、ウィンドウそのものは非表示の透明にしている。当然サイズ変更はできない。サイズ変更したい時だけウィンドウ枠を表示するようにするか?(これは簡単。コンテキストメニューとかでそういう項目を作ればいいだけ)

それともViewを直接サイズ変更して、それに連動してウィンドウも大きさを変えるようにするか?(こっちは難しそう)

移動は[window setMovableByWindowBackground:YES];としているのでマウスでドラッグすれば大丈夫。

という感じで今日は目がしくしく痛くなるまでiMacの前でXcodeを触っていた。楽しいなあ。

2011年2月19日土曜日

Cocoa ObjC Spin開発開始

超小物アプリSpinの開発を開始した。

機能は2つ。

1,Spacesの設定を読み込んで、Workspaceの数だけ小さなViewを表示する。そのViewをクリックすると該当のWorkspaceに切り替わる
2,マウスホイールをころころするとWorkspaceが切り替わる

予想実行画面。


やたらとだだっ広いiMacのデスクトップを使っている、キーボードよりマウス派、というなんだかすごくニッチな人向けのアプリになると考える。ま、自分用ですな。

しかし作る上ですごくCocoaフレームワークの勉強になるな。

Cocoa ObjC 起動中のアプリをSpacesごとに表示する

起動しているアプリを、表示しているWorkspaceごとに区分けして表示してみる。

ウィンドウを一つしか開かないアプリなら話は簡単だけれど、たくさんウィンドウを開くアプリの扱いで悩む。

とりあえず、開いているウィンドウを「1つのアプリ」とみなして表示する。こんな感じ。


現在のXcode3.2.5だとやたらたくさんウィンドウを開くことになるので、画像のようなことになる。試しにWorkspace4にデベロッパドキュメントのウィンドウを開いている。これをクリックすると、残念ながら「Xcodeの現在のアクティブなウィンドウ」がアクティブになる。


dockの右クリックで開くメニューの、一番最初に表示されているウィンドウが開く、と。

それなら、同じアプリのウィンドウは1つのWorkspaceにつき1つだけ表示するようにしてみる。


これならまあまあ、というところか。Workspace4のXcodeのアイコンをクリックするとWorkspace2のXcodeがアクティブになるのは相変わらず。(これはきっとどこかにどうにかするプライベート関数があるのではないか、と思ってずっと探しているが、まだみつからない。特定のウィンドウを選択してアプリをアクティブにする・・・できないかなあ)

ここまでの問題点は

1,ウィンドウの選択ができない
2,「全てのWorkspaceに表示する」を設定しているアプリが拾えない
3,Finderのアクティブ化が想定したようにならない
4,最小化しているアプリを拾っていない

という感じ。1は今のところどーにもならない。2は本日気づいた。


iTermを「全ての操作スペース」で表示する、にしたところ、「起動中のアプリ」として拾えなくなった。

これは原因はわかっていた、CGWindowListCopyWindowInfo( kCGWindowListOptionAll , kCGNullWindowID)で全ウィンドウの情報を取得して、
if (CFDictionaryContainsKey(w, kCGWindowWorkspace)) {
とWorkspace番号をもっているアプリだけ拾っているから。「全ての操作スペースで・・・」を選択しているアプリは、Workspace番号を持っていない。


ちなみに画像はxcatsan師匠がCGWindowListCopyWindowInfoのサンプルとして作ったツール。私のために作ってくれた(^^;)ようなツールでめちゃ使えますな。

3のFinder関連は、「現在のWorkspaceにFinderのウィンドウがないときはメニューバーでFinderがアクティブになるだけ」という現象。Fiderで一つでもウィンドウが開いているなら、それがアクティブになってほしいのだけれど、残念ながらそうはならない。Workspace1にFinderのウィンドウがなくて、Workspace2にFinderのウィンドウが一つ開いている、という状態だとすると、Workspace2でFinderのアイコンをクリックすると開いているウィンドウがアクティブになるけれど、Workspace1だとメニューバーでFinderがアクティブになってしまう、という感じ。

OSX、けっこうクセがありますなあ。

というわけで、Workspaceの操作とタスクスイッチャをいっしょにするにはちょっと無理がある、という結論に傾きつつある。

ワークスペース切替器とタスクスイッチャは別物として作ったほうがいいようだ。

2011年2月18日金曜日

Cocoa ObjC MatrixなView

NSMatrixとは全然関係ありません。自分で作る、Matrixな表示のViewの話。

本日の成果はこんな感じ。


基本的なViewの上に、四角いViewを並べる、と。おそらく
1,MainController
2,MainView
3,SpacesView
4,RunAppView
というクラス構成と名前になると思われ、画像はそのうちMainViewとSpacesViewにあたる予定。
画像で16個ある四角がSpacesView、1つがSpacesのWorkspace1つを表している(予定)。
MainViewの大きさとWorkspaceの数に合わせてSpacesViewの大きさは自動で設定される。当然MainViewがリサイズされたらSpacesViewもリサイズ。MainViewの大きさが同じでWorkspaceが4つだとこうなる。


この感じだとこんなふうにリサイズするのが一般的だろう。


で、実際にはこの位置、このくらいのサイズで使うつもり。


スクリーンの右隅に置くのが自分としてはいいかなあ。
ウィンドウのどこでマウスホイールをころころしてもWorkspaceが切り替わり、SpacesViewのクリックで該当Workspaceへの切替、さらにSpacesViewに表示されているアプリのアイコンで、タスクスイッチ、という感じで機能をつけていく予定。
実際のSpacesViewのサイズ・表示位置決定部分のソースはたったこれだけ。

-(void)setSpaceFrameSize{
 NSRect rect;
 NSRect bound=[self bounds];
 rect.origin.x=VIEW_FRAME_SPACE;
 rect.origin.y=(bound.size.height / ROW)*(ROW-1)+VIEW_FRAME_SPACE;
 rect.size.width=(bound.size.width / COL)-(VIEW_FRAME_SPACE * 2);
 rect.size.height=(bound.size.height / ROW)-(VIEW_FRAME_SPACE * 2);
 NSArray *views=[self subviews];
 if([views count]>0){ 
  for(NSInteger j=0; j< ROW ;j++){
   for(NSInteger i =0;i<COL;i++){
    //NSLog(@"x:%f y:%f w:%f h:%f",rect.origin.x,rect.origin.y,rect.size.width,rect.size.height);
    FrameView *aFrame=[views objectAtIndex:i+(COL*j)];
    aFrame.frame=rect;
    rect.origin.x+=(VIEW_FRAME_SPACE *2)+rect.size.width;
    
   }
   rect.origin.y-=(rect.size.height+VIEW_FRAME_SPACE*2);
   rect.origin.x=VIEW_FRAME_SPACE;

  }
 }
}


(bloggerに文句を言われるのでひとつだけ全角の<を使っております)
ROW、COLは現在のところdefineした定数。本番ではちゃんとSpacesのRowとColになるでしょう。

*NIXのWindowManagerに付属する(というかワークスペース切替器はWindowManagerの基本的な機能の一部ですな)ワークスペース切替器だと、現在表示されているウィンドウをリアルタイムに表示できるものですが、そこまでやる気力は今のところ、ない。というか必要ないような気もするし。

全体的な設計を考えてつくり直していくので、まだまだ完成まで時間がかかりそう。

2011年2月17日木曜日

Cocoa ObjC Warpのソースを読む

Warp

Spaces.appをちょいとパワーアップするプリファレンスアプリ。具体的にはスクリーンの上下左右の縁にマウスのポインタをもっていくとWorkspaceをスイッチする。Workspaceのリアルタイムプレビューを表示する機能もある。

このアプリでマウスホイールころころが実現できるなら、わざわざ自分でつくろうとはしなかっただろう、と思う。その意味で、Cocoaに触り始める契機となった恩人のようのアプリかも。

ありがたいことにオープンソースなのでソースを一式いただいてよく参照している。xcatsan師匠と同じくらい私淑していると言っていい。Kent Sutherlandちゃんありがとう。

タスクスイッチャもどきの機能がけっこうめどがついたので、いよいよObjC版のSpinをつくろうかな、と思っているが、もう少しViewについて研究する必要を感じる。具体的には、

1,起動中のアプリを表示する「アイコンボタン風」View
2,それをWorkspaceごとに区分けして表示する「Workspace視覚化」View
3,さらに2をSpacesのWorkspaceの数だけ表示する「Spaces視覚化」View
4,以上3つを操作するためのcontroller

というふうに4つのクラスで構成しようと思っている。どのクラスにどんな機能をもたせて、どこまで関連させてどこをカプセル化するのか、なかなか決められない。

これはおそらくObjC、Xcodeによるプログラミング経験が少ないからと思われ、それならすでに完成しているプロジェクトのソースを読んで勉強しよう、と。

さらに
詳解 Objective-C 2.0 改訂版
荻原 剛志
ソフトバンククリエイティブ
売り上げランキング: 20298

この本の第17章をじっくり読もうと思う。やっぱり基礎って大切だよなー。デリゲートについてよくわかっていないのが最悪、と自分でも思うんだなー。

2011年2月16日水曜日

xcatsan師匠 CGWindowListCreateImage

xcatsan師匠にCGWindowListCreateImageのふるまいについて質問してみた。新Cocoaの日々で。

師匠、見ず知らずの素人の質問に親切にあれこれと答えてくれて感涙。いい人だ。

しかし師匠の教えてくれたとおりにCGWindowListCreateImageでいろいろやってみるものの、どーもうまくいかない。だいたいこういう時は自分のせいなので、もう少しがんばってやってみよう。

問題は CGWindowListCopyWindowInfoの引数なのか、CGWindowListCreateImageなのか、CGWindowListCreateImageFromArrayなのか、それともCFArrayRefの扱い方なのか・・・。

それと同時に、やっぱり他のアプリのウィンドウを選択してアクティブにする方法もしつこく調べている。dockをあれこれするプライベート関数がないかな、とか。

実際に

meeu labs

というサイトで、_CoreDockというプレフィックスがつくプライベート関数があることを知る。引数も戻り値もわからないんだけど、

_CoreDockGetProcessWindow

とかいう関数もあるみたい。

こういう隠されたものを探っていくおもしろさってたしかにあるけれど、でもオープンソースならなにもかも白日のもとにさらされて、みんなで楽しもうぜ、ということになってるわけで、やっぱりそっちのほうがいいなあ。

Cocoa ObjC タスクスイッチャもどき(3)他のアプリの起動、終了を感知する

タスクスイッチャなので、自分が起動している間に他のアプリが起動されたり終了されたりしたら、ちゃんとそれを感知しないと話にならない。

デベロッパドキュメントを読むと、NSWorkspaceクラスのNotificationCenterで、NSWorkspaceDidLaunchApplicationNotification、NSWorkspaceDidTerminateApplicationNotificationをObserveすればいいことがわかった。さっそく実装。

NSWorkspace *ws=[NSWorkspace sharedWorkspace];
 [[ws notificationCenter] addObserver:self selector:@selector(appLaunched:) name:NSWorkspaceDidLaunchApplicationNotification object:ws];
 [[ws notificationCenter] addObserver:self selector:@selector(appTermed:) name:NSWorkspaceDidTerminateApplicationNotification object:ws];

Observerの設定はこれでOK。あとは@selectorを書くだけ。

-(void)appLaunched:(NSNotification *)notification{
 
 NSRunningApplication* app=[[notification userInfo] objectForKey:@"NSWorkspaceApplicationKey"];
 
 [self addAppAndButton:app]; 
}
addAppAndButtonは実際にaddViewをするメソッドで、たいしたことはしていない。

-(void)addAppAndButton:(NSRunningApplication *)app{
 
 if([app bundleIdentifier]!=[[NSRunningApplication currentApplication] bundleIdentifier]){
  NSRect viewFrame=NSMakeRect(0, 0, 10,10);
  SPApp *sp=[[SPApp alloc] initWithRunningApp:app];
  MyView *buttonView=[[MyView alloc] initWithFrame:viewFrame runApp:sp];
  [view addSubview:buttonView];
  [buttonView release];
  [sp release];
 }
}

自分自身を拾わないために

if([app bundleIdentifier]!=[[NSRunningApplication currentApplication] bundleIdentifier]){

としているが、こんなんでいいのかな。

他のアプリが起動すればaddSubViewすればいいわけだが、終了したらどうするか。こっちのほうが難しいような感じがしたが、

-(void)appTermed:(NSNotification *)notification{
 NSRunningApplication* app=[[notification userInfo] objectForKey:@"NSWorkspaceApplicationKey"];
 NSArray *views = [view subviews];
 for( MyView * a_view in views){
  if([[a_view _app] _pid]==[app processIdentifier]){
   [a_view removeFromSuperview];
  }
 }
 
}

意外とこれだけで大丈夫のようだ。[a_view removeFromSuperview];のあとに「break;」を入れたほうがよりいいかな。deallocがうまくいっているかどうか、調べる方法をみつけなければ(^^;)。

本日わかったことはNotificationで通知されるuserInfoからObjectw取り出すやりかた。rubyで言うところのHashと同じ、とは知っていたが、実際に自分でKeyからObjectを指定して、うまくいくかどうかちょっとどきどき。

subViewを削除するのも[a_view removeFromSuperview];だけでいいとは。拍子抜けするほど簡単だ。
もしかしてObjCって、rubyくらいユーザフレンドリなのか?

Cocoaフレームワークに触りだしてから、毎日が異常に充実している。家に帰ってきてiMac起動させるのが待ち遠しくて仕方がない。職場のPCがWindowsマシンでよかった。Macだったら仕事もしないでプログラミングしているところだった。(^^;)

2011年2月15日火曜日

Cocoa ObjC CGSSetUniversalOwner

CGSSetUniversalOwnerに行き着くまでの経過をメモ。

CGSPrivate.hの中にはウィンドウを操作するらしい関数がいくつかある。それが実際に動くか試してみた。

例えば

extern CGError CGSOrderWindow(const CGSConnection cid, const CGSWindow wid, CGSWindowOrderingMode place, CGSWindow relativeToWindowID /* can be NULL */);

これはきっとウィンドウを画面の一番上に持ってくる関数だろう。(いや、もしかしたら「常に一番上に表示」状態にするのかも・・・。ちゃんと試していないので実際は不明)

extern CGError CGSGetWindowAlpha(const CGSConnection cid, const CGSWindow wid, float* alpha);

これは明らかにウィンドウの透明度を設定する関数。どちらの関数も第1引数はCGSConnection cidで、これは次の関数で得ることのできる「現在のプロセスのコネクションID」。

/* Get the default connection for the current process. */
extern CGSConnection _CGSDefaultConnection(void);

自分のアプリケーションとCPUのプロセスを一意に結びつけている番号、みたいなものでしょうか。第2引数ではconst CGSWindow widを使うものが多い。これにはNSWindowクラスのプロパティwindowNumberを入れればいい。ただし、他のアプリのwinddowNumberを入れるとエラーになる。結局、const CGSConnection cidで操作できるのはほとんど自分のアプリのウィンドウだけ、らしい。

wid関連の操作でない時は自由にいろいろなウィンドウが操作できる。例えばこの関数。

extern CGError CGSMoveWorkspaceWindows(const CGSConnection connection, CGSWorkspace toWorkspace, CGSWorkspace fromWorkspace);

Workspace1にあるウィンドウを全部Workspace2へ、なんてことが簡単にできてしまう。

ここでこういう関数を発見。

extern CGError CGSGetConnectionIDForPSN(const CGSConnection cid, ProcessSerialNumber *psn, CGSConnection *targetConnection);

任意のアプリのConnectionIDを得ることができる?
それで得たIDを第1引数に指定すれば、任意のアプリの任意のウィンドウを操作できるのか?

というわけでProcessSerialNumberをデベロッパドキュメントで調べたら、

GetProcessForPID(pid ,&psn)
という関数があった。PIDがわかれば、そのProcessSerialNumberを得ることができる、と。そこでこんなふうに書いてみた。

 CGSConnection targetConnection;
 ProcessSerialNumber psn;
 
 GetProcessForPID([_app _pid] ,&psn);
 NSLog(@"%@:%d:%o",[_app _name],[_app _pid], psn.lowLongOfPSN);
 CGSGetConnectionIDForPSN(cid, &psn, &targetConnection);
 CGSOrderWindow(targetConnection, [sender tag], kCGSOrderAbove , 0);

第1引数を他のアプリのConnectionIDに替えてみたけれど・・やはりエラーになりました。orzだめか。

この他、ニセのNSEventをでっちあげてみる、とか、Notificationを投げてみる、とかやってみましたがことごとくだめ。

先日の3連休はこの試行錯誤でほとんどなくなりましたね。

cocoadev CGSPrivate.h

CGSPrivate.hはもともとこのフォーラムからコピペしてたわけだが、あとになって「Warp」のプロジェクトフォルダがコピーしたものを使っている。

本日、いろいろ調べているうちにまたまたこのフォーラムに行き着いて、ずーっとページをスクロールさせたらこんなことが書いてあるではないか。

So, I've tried writing a little utility that would change the alpha value of any window, but it only works for windows in my application... What can I do to make it work with any cocoa window?

おお、まさに私の知りたいことではないか!これに対するレスポンス。

You can't. A "special" connection to the WindowServer? is required to mess with other application's windows, and this connection is maintained exclusively by the Dock. If you quit the Dock you can take control of other windows... but good luck getting your users to ditch the dock :)

orz。そうか、そういうことだったのか。自分のアプリのウィンドウしか操作できないのか・・。
ま、納得しました。HyperDockが「ユニバーサルアクセス」を必要とする理由はこれか。Dockは不可触領域のようですな。
CGSSetUniversalOwnerのUniversalOwneが「maintained exclusively by the Dock」なんでしょう。

けっこうディープに調べた感を得ることができたここ数日だけど、結局やりたことができないとわかってフラストレーションが溜まってしまった。

次はGUI関連で実験していこう。まずは「Viewをリサイズする」かな。

Cocoa ObjC タスクスイッチャもどき(2)アプリのウィンドウ一覧を得る

タスクスイッチャまで
起動中のアプリをアイコンボタン風に表示して、そこからタスクを切り替える、というところまでは簡単にできる。

起動中のアプリを保存しておくデータ用のクラスは現在こんな感じ。

@interface SPApp : NSObject {
 NSString* _name;
 NSImage* _icon;
 pid_t _pid;
}
-(id)initWithRunningApp:(NSRunningApplication*)aApp;
@property(readonly) NSString* _name;
@property(readonly) NSImage* _icon;
@property(readonly) pid_t _pid;

@end

荻原本によるとインスタンス変数にアンダースコアつけるのはやめよ、ということなんだけど・・・。

で、このクラスにNSRunnnigApplicationのインスタンスからデータをコピーして、表示用のViewに渡す。

-(void)setRunningApp{
 NSWorkspace *ws=[NSWorkspace sharedWorkspace];
 NSArray* ra=[ws runningApplications];
 
 for(NSRunningApplication* app in ra){
  if([app activationPolicy]==NSApplicationActivationPolicyRegular){
   NSRect viewFrame=NSMakeRect(0, 0, 10,10);
   SPApp *sp=[[SPApp alloc] initWithRunningApp:app];
   MyView *buttonView=[[MyView alloc] initWithFrame:viewFrame runApp:sp];
   [view addSubview:buttonView];
   [buttonView release];
   [sp release];
  
  }
 }
 
}

クラス名がMyViewなのはご愛嬌。(^^;)
このままだとViewのrectが(0,0,10,10)なんだけど、これはViewの中でサイズを定義しなおしている。その辺はごちゃごちゃしたコードになっているので省略。

で、このViewのmouseUpあたりでこんなふうに書いてタスクスイッチャ機能は完成。
-(void)mouseUp:(NSEvent *)theEvent{
 [self set_state:BUTTON_STATE_OVER];
 [self setNeedsDisplay:YES];
 NSRunningApplication *selectApp=[NSRunningApplication runningApplicationWithProcessIdentifier:[_app _pid]];
 [selectApp activateWithOptions:NSApplicationActivateIgnoringOtherApps]; 
 

}


で、さらに、どーせなら起動中のアプリが開いているウィンドウの一覧を表示して、ウィンドウを選択してタスクを切り替えるようにしたい、と考えた。

ここまでが前段。
開いているウィンドウの一覧
ユニバーサルアクセスを無効にしたら、「HyperDock」が使えなくなって、ちょっと難儀している。HyperDockはDock上にWinVISTA、WIN7のタスクバー風にウィンドウの一覧を表示するユーティリティ。


これがあるとXcodeとInterfaceBuilderでたくさんウィンドウが開いても、すぐに目的のウィンドウをアクティブにできる。HyperDockが使えなくなったのでExposeを使うようになったけれど、全画面を使って「ウィンドウの一覧」を表示するため、アプリの選択をまちがったら、やり直す作業がちょっと煩雑。
そこでHyperDockもどきの機能をつけようとしてみた。

まずはウィンドウの一覧を得る。

あとになってxcatsan師匠が同じことを書いているのに気づいたんだけど、一応、半分くらいは自分でこの方法を考えた。

CGWindowListCopyWindowInfoを使えば全アプリのウィンドウの一覧を表示できる。これはxcatsan師匠が便利なサンプル付きで解説してくれているので知っていた。

で、ウィンドウの親プロセスもPIDで得ることができるので、起動中のアプリのPIDと同じPIDをもっているウィンドウがそのアプリのもの、ということになる。

-(void)rightMouseDown:(NSEvent *)theEvent{
 CFStringRef ownerPid;
 CFIndex i;
 CFDictionaryRef w;
 CFStringRef winName;
 CGRect rect; 
 CGWindowID spot_window_id;

 
 CFArrayRef list =CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
 
 NSMenu *menu = [[[NSMenu alloc] initWithTitle:@"HELLO"] autorelease];
 for (i=0; i < CFArrayGetCount(list); i++) {
  w = CFArrayGetValueAtIndex(list, i);
  if (CFDictionaryContainsKey(w, kCGWindowWorkspace)) {
   ownerPid=CFDictionaryGetValue(w, kCGWindowOwnerPID);
   if ([(NSString*)ownerPid intValue] ==[_app _pid] ) {
    winName=CFDictionaryGetValue(w, kCGWindowName);
    if((NSString *)winName !=@""){
     CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(w, kCGWindowBounds), &rect);
     
     CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowNumber),kCGWindowIDCFNumberType, &spot_window_id);

本当に全てのウィンドウの一覧なので、
if (CFDictionaryContainsKey(w, kCGWindowWorkspace)) {
この条件を挟んで「Workspace上にウィンドウとして表示されている」ものに限定する。で、同じPIDならウィンドウのタイトルと(winName)、表示領域(rect)と、windowID(spot_window_id)を取得する。 で、とりあえずウィンドウを画像として表示してみる実験をした。
     CGImageRef cgimage = CGWindowListCreateImage(rect,kCGWindowListOptionIncludingWindow , spot_window_id, kCGWindowImageDefault);
     NSSize imageSize=NSMakeSize((int)rect.size.width*0.25, (int)rect.size.height*0.25);
     NSImage* newImage=[[[NSImage alloc ]initWithCGImage:cgimage size:imageSize] autorelease];
     [newImage drawAtPoint:NSMakePoint(0, 0) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
CGWindowListCreateImageでえたCGImageRefを使ってinitWithCGImagでNSImageを作っている。その時原寸の4分の1に縮小している。それをウィンドウの(0,0)で描画する。
表示されたときは飛び上がって喜びましたよ。こういう、グラフィックス系の操作をやったのはPalmOSでjpegとbitmapをちょっこと使おうとしたことがあるだけなので。 しかし・・・・残念ながらこれだと、ちがうSpaceにあるアプリのイメージは取得できない。CGWindowListCreateImageのオプションをいろいろいじってみたけれど、せいぜい「アクテイブなWorkspaceの、該当するrectを画像として生成する」ことしかできない。 あれじゃあ、SimpleCapはどういう動作をするんだったけ、と思って試してみたら、やはり非アクティブなWorkspaceに表示されているアプリのキャプチャはできなかった。xcatsan師匠に図々しく問い合せてみたら、これは初めからの仕様ということだった。
メニューにしてみる
画像をどうしても取得できないなら、とりあえずメニューにしておいてウィンドウをアクティブにできるか試す。
     NSMenuItem *item=[[NSMenuItem alloc] init];
     [item setTitle:(NSString *)winName];
     [item setTarget:self];
     [item setAction:@selector(select:)];
     [item setTag:(NSUInteger) spot_window_id];
     [menu addItem:item];

もちろんこれはうまくいく。
しかし・・ここからわからない。どーしても「あるアプリのあるウィンドウ」にアクセスする方法がわからない。 要はDockの右クリックから出てくるメニュー、
これと同じことがしたいわけだ。メニューとして表示することまではできた。ではどうやってウィンドウを選択するのか。
結論:できない
普通の方法ではできないことがわかった(^^;)。CGSPrivateを使うのではないか、と当たりをつけて調べてみたら、
CGSSetUniversalOwner
がそれを実現する方法。ただし、これはDockをkillして、それから自分のConectionIDをDockの代わりに登録する、という関数。つまりDockの再発明をする、ということになる。

アホらしくて実際に試していない。それならDockを使えばいいわけだから。

モバギ復活!

121ware.com > 製品情報 > LifeTouch NOTE

webOSの新製品にも、NokiaとMSの提携にも反応しなかったけど、この記事にはぶっ飛んだ。

モバギが復活した!


内蔵4Gメモリ+4GのSDで39,900円とな。
完全におもちゃだよなあ、もしも買ったとしたら。うーん。

2011年2月14日月曜日

Skim 1.3.11(日本語化バージョン)

MLに告知されてました。Skimの新しいverがダウンロード可能。

今回のVersionUpには日本語化が含まれております。担当は不肖ワタクシ。訳に変なところがあったら教えてくだされ。

ダウンロードはこちらから。

Cocoa ObjC タスクスイッチャもどき

起動中のアプリの一覧を、こんなふうに表示してみた。


一応、アイコン+アプリ名の横長版、アイコン+アイコンの下にアプリ名、アイコンのみ、という3つの表示方法を取れるようにしてみた。

書き散らかしの実験用プロジェクトなもので、クラス名がいちいちMyViewだったりCustumViewだったりするので(^^;)、その辺をすこし整理しながら書きなおそうと考えている。現在は

1,コントローラで起動中のアプリを調査して専用のクラスに入れて保持
2,それをもとに「アプリ1つを保持して表示するViewクラス」をインスタンス化
3,さらにそいつを「並べて表示する専用View」にaddSubview

という感じ。いろいろコードを書いては直し、直しては書き、としていると、コントローラはいらないな表示用のViewでデータを保持したほうがいいじゃん、とか、NSRunnnigApplicationクラスが作ってくれる「起動中アプリの一覧」Arrayをそのまま使ったほうがいいんじゃね?とか、アイディアだけは湧いてくるものだ。こうやってのめりこんでいくのが面白いんだなあ。

アプリを保持しているView(ちゃんとクラス名考えないとだめだな(^^;))はマウスのトラッキングとかもできるようにしてみた。xcatsan師匠のコードのカットアンドペーストだけど。


これでアイコンを左クリックすると、そのアプリがアクティブになる。

NSRunningApplication *selectApp=[NSRunningApplication runningApplicationWithProcessIdentifier:[_app _pid]];
 [selectApp activateWithOptions:NSApplicationActivateIgnoringOtherApps]; 
アプリケーションのpidをコピーしてあるので、これだけでアクティブにできる。おもしろー。

2011年2月13日日曜日

Cocoa ObjC 最初のマウスクリックでマウスイベントを受け取る

すぐ忘れるので備忘録。

-(BOOL)acceptsFirstMouse:(NSEvent *)theEvent{
return YES;
}

プロパティとかでセットするのではなくこのハンドラをどこかに書く。

Cocoa Objc 非アクティブ時でもマウストラッキングを有効にする

これも備忘録。

自分が非アクティブの時でもマウストラッキングは有効にする。トラッキングエリア登録時のオプション。
_tracking_area = [[NSTrackingArea alloc] initWithRect:tracking_rect
              options:(NSTrackingMouseEnteredAndExited |
                 NSTrackingMouseMoved |
                  NSTrackingActiveAlways)
             owner:self
             userInfo:nil];
オプション3つめの NSTrackingActiveAlwaysが重要、と。

2011年2月12日土曜日

OSX fluxboxを野良ビルドする

自分用のアプリを作っていて、どーやら作りたいのは、Linuxデストリビューションで言うところのいわゆる「デスクトップ切替器」、なのではないか、と気づいた。

で、いろいろググったりしているうちにOSXでfluxboxをX11のウィンドウマネージャとして使う、という記事に出会う。

ほほう、ということで、さっそく自分でも野良ビルドしてみた。ソースをtarballで落としてきて、configure make make installというなんのことない作業だった。簡単すぎて気が抜けますな。

ただしconfigureには以下のオプションが必要。

./configure --x-include=/usr/X11R6/include --x-libraries=/usr/X11R6/lib

ふむ、えらそうに書くほどのこともないか。(^^;)

で、.xinitrcを以下のように書く。

#This line sets your path environment
PATH=$PATH:/usr/X11R6/bin:/sw/bin:/usr/local/bin
export PATH

# Opens up an xterm
xterm -geometry -250+200 &

# opens up the oclock clock
oclock -geometry -100+50 &

# Runs the fluxbox window manager
exec /usr/local/bin/fluxbox

これはどこやらで見かけたインストール記事からカット&ペーストしたもの。

これでX11.appを起動すると、ウィンドウマネージャがfluxboxに。


ちゃんと、えーとこのバーの名前はなんだったっけ(^^;)、とにかくステータスバーまがいのこいつが起動する。このバーの上でマウスホイールをころころすると、x11のデスクトップが切り替わる。Spacesのworkspaceは変わらない。おもしろいもんですな。Spacesの設定で、x11を全部のworkspaceで表示するようにしたら、Spacesのworkspace×x11のデスクトップ、という数の仮想デスクトップになるのかしらん。


ウィンドウの表示がしょぼいのでテーマを替えたいけれど、デスクトップでのクリックが効かない(Finderが全部もっていきますな、もちろん)ので面倒になってやめた。

fluxboxなので、起動できたからといってそれほどうれしいものでもない。(^^;)じゃあ他のウィンドウマネージャはビルドできるの?という当然の好奇心が湧き上がる。

そこでWindowMaker、afterstepという、NextStepインスパイア組をビルドしてみたところ、どーも64bit環境でのビルドは具合がわるいらしくてエラー終了。

ちなみにどちらもportsでは入れられるみたい・・・なのかな。こっちも64bit問題があったような気もする。

ということで野良ビルドはこれにて終了。

Cocoa ObjC Spaces 起動中のアプリケーション一覧

現在起動中のアプリケーションの一覧を、NSViewに表示する。

なんとかここまでできた。

起動中のアプリケーションを得るのは簡単。
-(void)setRunningApp{
 NSWorkspace *ws=[NSWorkspace sharedWorkspace];
 NSArray* ra=[ws runningApplications];
 
 for(NSRunningApplication* app in ra){
  if([app activationPolicy]==NSApplicationActivationPolicyRegular){
   SPApp *sp=[[SPApp alloc] initWithRunningApp:app];
   
   [_spAppArray addObject:sp];
  
  }
 }
 
}


基本的なことは下の2行だけで可能。
NSWorkspace *ws=[NSWorkspace sharedWorkspace];
NSArray* ra=[ws runningApplications];

この辺のことは以前MacRubyでやった経過を書いている。

MacRuby・Snatcher・タスクセレクタ(2)

今回はメニューに表示するわけではなく、Viewの勉強を兼ねて自分でなんとかしてみよう、と努力している。

現在のところ、

1,MyControllerがアプリの一覧を保持
2,アプリ1個分のデータ(名前、アイコン、PID)を受け取ってそれをボタン風に表示するためのView
3,上のViewをsubViewとして受け取って、表示する基本のView

という3つクラスに分けている。xcatsan師匠の「ThinButton」シリーズ(現在師匠のページが重くて表示できず、リンクははれません)を参考にしながら作業してみた。

アプリの一覧のデータをコントローラが持つのがいいのか、基本のViewが持っていてボタン風のViewを生成するのがいいのか、あれこれ悩みながら試行錯誤。せっかくクラスをわけて作るのだから、できるだけ相互依存のない状態にして役割分担をはっきりさせたほうがいいと思うのだけど・・・初心者はその辺の判断に苦しむ。

最初の画像の状態まで表示することはできたけど、コードはあまりにも稚拙で汚い(座標の数値とかもろに数字で書いているし)ので割愛。

でもCocoaプログラミングはおもしろいなー。もっと詳しく知りたいなー、と思う。

2011年2月11日金曜日

atMonitor

Macの強力な無料のシステムモニタリングツール『atMonitor』 : ライフハッカー[日本版]

上記ライフハッカーの記事にて初めて知る。さっそく本家サイトからダウンロードして使ってみた。


なかなかよさそう。起動中のアプリ・プリファレンスの情報を詳細に得ることができる。

プログラミングしていると、人の作ったアプリでの「見せ方」がすごく気になる。お、ここでHUDのパネルを使ってるのか、みたいな。

Cocoa ObjC 初めてクラスを書いてみる

1からまだクラスを書いたことがなかった。ちょうど「実行中のアプリ情報」を保持する仕組みが必要になったので自分で書いてみる。

人のコードをカットアンドペーストして、じっくり読んでそれを応用する、というスタイルで今のところきている。初心者にはちょうどいい勉強法。しかし実際に自分で書くとなると、知識がいかにいいかげんか痛感する。

とりあえず、実行中のアプリを保持するためのクラスのヘッダ。
#import 


@interface SPApp : NSObject {
 NSString* _name;
 NSImage* _icon;
 pid_t _pid;

}
-(id)initWithRunningApp:(NSRunningApplication*)aApp;
@property(readonly) NSString* _name;
@property(readonly) NSImage* _icon;
@property(readonly) pid_t _pid;

@end


そしてimplementation。
#import "SPApp.h"
#define ICON_SIZE 32

@implementation SPApp
@synthesize _name;
@synthesize _icon;
@synthesize _pid;
-(id)initWithRunningApp:(NSRunningApplication *)aApp{
 self=[super init];
 if (self) {
  _name=[[aApp localizedName] retain];
  _icon=[[aApp icon] retain];
  [_icon setSize:NSMakeSize(ICON_SIZE,ICON_SIZE)];
  _pid=[aApp processIdentifier];
 }
 return self;
}
-(void)dealloc{
 [_name release];
 [_icon release];
 [super dealloc];
}
@end

dealocが本当に必要なのか今イチ自信がない。しかし今のところ不都合はないようだ。クラッシュとか。プロパティとかアクセサとか、書いてみないと書き方がわからないものだなあ。

retainってどこでどうやって使うんだかまだまださっぱり理解していないが、ここでつけないとコンパイラに怒られる。

自分でコードを書くときは

詳解 Objective-C 2.0 改訂版
荻原 剛志
ソフトバンククリエイティブ
売り上げランキング: 2710

この本と首っ引き。買っておいてよかった。これがないと到底クラスなんか書けなかったぞ。

MacRubyで書きかけていたアプリは、タスクスイッチャみたいな機能をつけたところで止まっている。起動中のアプリを取得して、それをサブメニューの中に仕込んで、というところまでいって、じゃあメニュー以外の表現方法にしてみよう、となってなぜかObjCの勉強をしている。本当に気まぐれだな。
MacRubyでつくっていた「Spin」と同じような機能のアプリを作成中で、今度は透明ウィンドウの中に起動中のアプリを何らかの方法で表示しようとしている。

はたしてうまくいくか・・・。Viewの勉強をもっとしなければ。

2011年2月10日木曜日

Cocoa NSImageView

基本的なことをちゃんと勉強しようということで、NSViewやNSImageViewなどを触ってみる。InterfaceBuilderでインスタンスを生成せず、コントローラーなどから動的に生成して、addSubViewとかをしてみる、と。

xcatsan師匠がSimpleCapを作る過程で山ほどこの手の処理を書いていてくれているので、あれこれ参考にしながら作業をしてみる。

まずはNSImageViewにデスクトップのキャプチャ画像を表示させてみた。コントローラのawakeFormNibでviewを生成する。
@implementation MyController
-(void)awakeFromNib{
 NSRect viewFrame = [[window contentView] bounds];
 MyImageView *view=[[MyImageView alloc] initWithFrame:viewFrame];
 [[window contentView] addSubview:view];
 [view release];
}
@end
これだけならInterfaceBuilderで生成してもいいわけだけど、ま、あとあといろいろ変更してみようと考えている。

Viewはカスタムクラスにして、こんな感じ。
@implementation MyImageView
-(id)initWithFrame:(NSRect)frameRect{
 self = [super initWithFrame:frameRect];
 if (self) {
  [self setCaptureImage]; 
 }
 return self;
}
-(void)setCaptureImage{
 CGWindowID window_id = kCGNullWindowID;
 
 CGImageRef cgimage = CGWindowListCreateImage(CGRectInfinite, kCGWindowListOptionOnScreenBelowWindow,window_id, kCGWindowImageDefault);
 NSBitmapImageRep *bitmap_rep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
 NSImage *image = [[[NSImage alloc] init] autorelease];
 [image addRepresentation:bitmap_rep];
 [bitmap_rep release];
 [self setImage:image];
 CGImageRelease(cgimage);
 [self setImageScaling:NSImageScaleAxesIndependently];
 NSLog(@"%d",[self imageScaling]);
}

setCaptureImage内の処理はほぼxcatsan師匠のコードの引き写し。これを自分で書けるようになるのが当面の目標だな。

これでデスクトップ全体のキャプチャ画像がViewいっぱいに縮小されて表示できる。


NSImageViewの場合、drawRect:をオーバーライトしたら自動でimageを表示してくれなくなることが本日わかった。(^^;)もう、素人なんだから仕方ないわな。