NSOutlineView/NSTreeControllerのサンプル読み-2

今日はメインの処理を実行しているMyWindowControllerを読んでみました。もちろん全部は読めていないのですが、awakeFromNibを起点にOutlineViewにノードを追加しているところを中心に読んでみました。ノードを作成するための処理は別スレッドで実行されています。

[NSThread detachNewThreadSelector:
    @selector(populateOutlineContents:)  toTarget:self withObject:nil];

こういうところは非常に参考になりますね。自分ではなかなかこういう実装は思いつかないですよ。で、本体のpopulateOutlineContentsは、

- (void)populateOutlineContents:(id)inObject
{
  // サブスレッドでキックされるのでプールの生成をしておく
  NSAutoReleasePool* pool = [[NSAutoreleasePool alloc] init];

  // ツリー作成中フラグ ON
  buildingOutlineView = YES;

  // ちらつき防止?
  [outlineView setHidden:YES];

  // ここがノード追加の処理
  [self addDevicesSection];
  [self addPlacesSection];
  [self populateOutline];

  // 後始末
  buildingOutlineView = NO;
  [outlineView setHidden:NO];
  [pool release]
}

とシンプル。気をつけるのはAutoreleasePoolをつくっておくくらいでしょうか?あとは、addDevicesSectionなんかを読み込んでいけばいいわけです。最初はデバイスをツリー表示しているaddDevicesSectionから。

- (void)addDevicesSection
{
  // グループノードの追加
  [self addFolder:DEVICES_NAME];

  // ローカルマウントボリューム名の取得
  NSArray* mountedVols = 
    [[NSWorkspace sharedWorkspace] mountedLocalVolumePaths];

  if ([mountedVols count] > 0)
    {
      for(NSString* element in mountedVols)
        { // 子ノードを追加
          [self addChild:element withName:nil selectParent:YES];
        }
    }
  [self selectParentFromSelection];
}

こちらも比較的シンプル。まず、最上位のグループの作成が[self addFolder:DEVICES_NAME]。次に、デバイス一覧を取得して子ノードとして追加するために[self addChild:element withName:nil selectParent:YES]を呼び出しています。まず、addFolderから

- (void)addFolder:(NSString *)folderName
{
  // メインスレッドとのインタフェース用オブジェクトの生成
  TreeAdditionalObj *treeObjInfo = 
    [[TreeAdditionalObj alloc] initWithURL:nil withName:folderName selectItsParent:NO];

  // アウトライン作成中
  if (buildingOutlineView)
    {
      [self performSelectorOnMainThread:@selector(performAddFolder:) 
         withObject:treeObjInfo waitUntilDone:YES];
    }
  else
    {
      [select performAddFolder:treeObjInfo];
    }
}

このメソッドも構造はシンプル。

インタフェース用のTreeAdditionalObjオブジェクト(クラスファイルの頭でこぢんまりと定義されています)を生成しperformAddFolderメソッドを呼ぶだけ。ただし、ツリー作成フラグがYES(=サブスレッドで実行中)の場合メインスレッドに戻ってこのperformAddFolderを実行しています。なぜメインスレッドに戻して実行するのか?今の段階では想像できませんでしたがここでとそこまで考え出すと進みませんからここでは忘れます。なにかメインスレッドでなければならない理由があるのでしょう。(UIの操作はメインスレッドでないとできないとか?)

// ツリーにグループを作成する
-(void)performAddFolder:(TreeAdditionObj *)treeAddition
{
  NSIndexPath* indexPath = nil;

  // 現在ツリーコントローラーが何も選択していなければ新しいグループを最後に追加する
  if ([[treeController selectedObjects] count] == 0)
    { // 新しいルートとして最後に追加
      indexPath = [NSIndexPath indexPathWithIndex:[contents count]];
    }
  else
    {  // 選択されているインデックスパスを取得
      indexPath = [treeController selectionIndexPath];

      // 選択されているノード(複数)の先頭がリーフノードか?
      if ([[[treeController selectedObjects] objectAtIndex:0] isLeaf])
        {  // リーフノードの親を選択する
          [self selectParentFromSelection];
        }
      else
        { // グループノードの場合、最後の子ノードとなるインデックスパスを取得
          indexPath = [indexPath indexPathByAddingIndex :
                [[[[treeController selectedObjects] objectAtIndex:0] children] count]];
        }
    }
  // ノード生成
  ChildNode* node = [[ChildNode alloc] init];
  [node setNodeTitle:[treeAddition nodeName]];
  //Outlineにノードを追加
  [treeController insertObect:node atArrangedObjectIndexPath:indexPath];
  [node release];
}

ここでTreeControllerの登場。最初に行っているのはOutlineに追加するインデックスパスの確定。ここで追加するインデックスパスは

  1. ルートノード
  2. 選択されているリーフノードの兄弟
  3. 選択されているグループのノードの末っ子

の三つのパタンです。二番目の挙動は要注意。明日以降再検証してみるつもりです。

ルートへの追加インデックスパスは、[contents count]ですからcontentsへはルートオブジェクトが格納されているのでしょう。0,1,2,3...となります。リーフノードが選択されている場合は現在選択中のノードのインデックスパスがそのまま使われます(先にも書いたように要注意かも)がその親ノードを選択し直し(selectParentFromSelection)ています。グループノードが選択されている場合はその末子のインデックスパスを生成しています。

追加するインデックスパスが確定したらいよいよ追加。ノードオブジェクト(ChildNodeはBaseNodeの派生クラス)をアロケートしてタイトルを設定、確定したインデックスパスを使って追加します。

今日は、子ノードの追加まで読み込めませんでした。MyWindowControllerを読んだ感じからすると、ノードクラスで親だの子だのすることはあまりなくてそういうことはTreeControllerに任せておけばいいのではないかという印象。ノードクラスは後回しにすればよかったかなぁ。明日以降、子ノードの追加のところを読み込んでいく予定。

この記事のトラックバックURL:

http://hippos-lab.com/blog/trackback/222

Comments


Apple Store(Japan)