章 14. 系統資訊

NSOutlineView 是 NSTableView 的子類別. 唯一的差別是 NSOutlineView 的資料來源是樹狀結構, 可以展開或關閉, 而 NSTableView 則是使用陣列記錄每一筆資料. 在這裡示範使用 NSOutlineView 顯示系統相關資訊.

使用 Gorm 產生一個新的程式 (New Application). 拖一個 NSOutlineView 到視窗中.

圖形 14.1. 系統資料的使用者介面

系統資料的使用者介面

改變 NSOutlineView 的自動改變大小屬性.

圖形 14.2. NSOutlineView 的自動改變大小屬性

NSOutlineView 的自動改變大小屬性

在 column 上點兩下以選擇該 column

圖形 14.3. 選擇 NSOutlineView 的 column

選擇 NSOutlineView 的 column

取消 "Editable" 並將第一個 column 的 identifier 改成 "Attribute", 第二個改成 "Value".

圖形 14.4. 改變 NSTableColumn 屬性

改變 NSTableColumn 屬性

使用者介面完成了. 要製做這個使用者介面的控制者. 繼承自 NSObject, 產生一個 "Controller" 類別.

圖形 14.5. 在 Gorm 產生子類別.

在 Gorm 產生子類別.

加入一個 outlet 稱為 "outlineview".

圖形 14.6. 產生 outlet

產生 outlet

產生 Controller 物件. 將 "outlineview" outlet 連到 NSOutlineView 上. 要確認不是連到 NSScrollView 或 NSTableColumn.

圖形 14.7. 連接 outlet 至 NSOutlineView

連接 outlet 至 NSOutlineView

NSOutlineView 是 NSTableView, 因此也必需指定其資料來源 (data source) 及代理者 (delegate). 連結 NSOutlineView 的 dataSource 及 delegate 至 Controller.

圖形 14.8. 連結 NSOutlineView 的 dataSource 及 delegate

連結 NSOutlineView 的 dataSource 及 delegate

最後, 指定 Controller 為 NSApp 的代理者.

圖形 14.9. 指定 NSApp 的代理者

指定 NSApp 的代理者

將這個使用者介面存成 Overview.gorm 檔. 產生 Controller 的程式碼.

因為 NSOutlineView 的資料是樹狀結構, 在這裡使用很簡單的結點 (Node) 來建立樹狀結構.

Node.h

#ifndef _Overview_Node_
#define _Overview_Node_

#include <Foundation/NSObject.h>

@class NSArray;
@class NSMutableArray;
@class NSString;

@interface Node: NSObject
{
  NSString *name;
  NSString *value;
  NSMutableArray *children;
}

- (void) setName: (NSString *) name;
- (NSString *) name;
- (void) setValue: (NSString *) value;
- (NSString *) value;
- (void) addChild: (id) child;
- (NSArray *) children;
@end

#endif /* _Overview_Node_ */

Node.m

#include "Node.h"
#include <Foundation/Foundation.h>

@implementation Node

- (id) init
{
  self = [super init];

  children = [NSMutableArray new];

  return self;
}

- (void) setName: (NSString *) string
{
  ASSIGN(name, string);
}

- (NSString *) name
{
  return name;
}

- (void) setValue: (NSString *) string
{
  ASSIGN(value, string);
}

- (NSString *) value
{
  return value;
}

- (void) addChild: (id) child
{
  [children addObject: child];
}

- (NSArray *) children
{
  return children;
}

- (void) dealloc
{
  RELEASE(name);
  RELEASE(value);
  RELEASE(children);
  [super dealloc];
}

@end

接下來會使用這些結點來建立樹狀結構.

NSTableView 需要這兩個 methods 來顯示資料:

- (int) numberOfRowsInTableView: (NSTableView *) tableView;

- (id) tableView: (NSTableView *) tableView
       objectValueForTableColumn: (NSTableColumn *) column
       row: (int) row;

因為樹狀資料結構的關係, NSOutlineView 需要四個 methods 來顯示資料:

- (id)outlineView: (NSOutlineView *)outlineView 
            child: (int)index 
           ofItem: (id)item;

- (BOOL)outlineView: (NSOutlineView *)outlineView
        isItemExpandable: (id)item;

- (int)outlineView: (NSOutlineView *)outlineView
       numberOfChildrenOfItem: (id)item;

- (id)outlineView: (NSOutlineView *)outlineView 
      objectValueForTableColumn:(NSTableColumn *)tableColumn 
      byItem:(id)item;

"Item" 即為資料結構中的結點, 如果 Item 是 nil, 表示在尋問根結點 (root node). 從根結點開始, NSOutlineView 會尋問每一個結點有多少個子結點, 這個結點是否能展開, 顯示結點的資料, 以及取得所有的子結點. 當 NSOutlineView 尋問過所有節點之後, 所有的資料便顯示出來了.

因此, 要先把樹狀結構建立起來:

Controller.h

/* All Rights reserved */

#include <AppKit/AppKit.h>

@class Node;

@interface Controller : NSObject
{
  id outlineview;
  Node *root;
  
}
@end

Controller.m

/* All Rights reserved */

#include <AppKit/AppKit.h>
#include "Controller.h"
#include "Node.h"

@implementation Controller

- (id) init
{
  Node *child, *temp;

  self = [super init];

  root = [Node new];

  child = [Node new];
  [child setName: @"System"];

  /* Add operating system */
  temp = [Node new];
  [temp setName: @"Operating System"];
  [temp setValue: [[NSProcessInfo processInfo] operatingSystemName]];
  [child addChild: temp];
  RELEASE(temp);

  /* Add user name */
  temp = [Node new];
  [temp setName: @"User Name"];
  [temp setValue: NSUserName()];
  [child addChild: temp];
  RELEASE(temp);

  /* Add home directory */
  temp = [Node new];
  [temp setName: @"Home Directory"];
  [temp setValue: NSHomeDirectory()];
  [child addChild: temp];
  RELEASE(temp);

  [root addChild: child];
  RELEASE(child);

  return self;
}

- (void) dealloc
{
  RELEASE(root);
  [super dealloc];
}

首先建立根節點, 加上一個 "System" 節點. 再加入三個節點進入 "System" 節點. 這是一個非常簡單的樹狀結構. 有了樹狀結構, 便可以在 NSOutlineView 中顯示出來.

Controller.m

- (id) outlineView: (NSOutlineView *) outlineView
             child: (int) index
            ofItem: (id) item
{
  /* Root */
  if (item == nil)
    return [[root children] objectAtIndex: index];

  /* Others */
  if ([[item children] count])
    return [[item children] objectAtIndex: index];
  else
    return nil;
}

- (BOOL) outlineView: (NSOutlineView *) outlineView
         isItemExpandable: (id) item
{
  /* Root */
  if (item == nil)
    return YES;

  /* Others */
  if ([[item children] count])
    return YES;
  else
    return NO;
}

- (int) outlineView: (NSOutlineView *) outlineView
        numberOfChildrenOfItem: (id) item
{
  /* Root */
  if (item == nil)
    return [[root children] count];

  /* Others */
  return [[item children] count];
}

- (id) outlineView: (NSOutlineView *) outlineView
       objectValueForTableColumn: (NSTableColumn *) tableColumn
       byItem: (id) item
{
  if ([[tableColumn identifier] isEqualToString: @"Attribute"])
    return [(Node *)item name];
  else
    return [item value];
}

程式碼很清楚, 只是要特別處理根結點的狀況. 當 item 為 nil 時, 就是尋問根結點的資料.

再完成 main.mGNUmakefile 即可. 程式碼在此: Overview-src.tar.gz

了解如何在 NSOutlineView 中顯示資料後, 便可輕易的加入更多資料.

Controller.m

- (id) init
{
  Node *child, *temp;
  NSCalendarDate *date;
  NSRect frame;
  id object;

#define ADD_NAME_VALUE(name, value) \
        temp = [Node new]; \
        [temp setName: name]; \
        [temp setValue: value]; \
        [child addChild: temp]; \
        RELEASE(temp);


  self = [super init];

  root = [Node new];

  child = [Node new];
  [child setName: @"System"];

  /* operating system */
  ADD_NAME_VALUE(@"Operating System", [[NSProcessInfo processInfo] operatingSystemName]);

  /* user name */
  ADD_NAME_VALUE(@"User Name", NSUserName());

  /* home directory */
  ADD_NAME_VALUE(@"Home Directory", NSHomeDirectory());

  /* gnustep root directory */
  ADD_NAME_VALUE(@"GNUstep Directory", NSOpenStepRootDirectory());

  /* host and address */
  ADD_NAME_VALUE(@"Host", [[NSHost currentHost] name]);
  ADD_NAME_VALUE(@"Address", [[NSHost currentHost] address]); 

  /* Screen */
  ADD_NAME_VALUE(@"Screen Depth", [[NSNumber numberWithInt: [[NSScreen mainScree
n] depth]] description]);
  frame = [[NSScreen mainScreen] frame];
  object = [NSString stringWithFormat: @"%d x %d", (int)frame.size.width, (int)f
rame.size.height];
  ADD_NAME_VALUE(@"Screen Size", object);

  [root addChild: child];
  RELEASE(child);

  child = [Node new];
  [child setName: @"Date & Time"];

  /* Time Zone */
  ADD_NAME_VALUE(@"System Time Zone", [[NSTimeZone systemTimeZone] timeZoneName]
);
  ADD_NAME_VALUE(@"Local Time Zone", [[NSTimeZone localTimeZone] timeZoneName]);

  /* Date */
  date = [NSCalendarDate calendarDate];
  [date setCalendarFormat: @"%a, %b %e, %Y"];
  ADD_NAME_VALUE(@"Date", [date description]);
  [date setCalendarFormat: @"%H : %M : %S"];
  ADD_NAME_VALUE(@"Time", [date description]);

  [root addChild: child];
  RELEASE(child);

  child = [Node new];
  [child setName: @"Text Related"];

  /* default encoding */
  ADD_NAME_VALUE(@"Default Encoding", [NSString localizedNameOfStringEncoding: [NSString defaultCStringEncoding]]);

  /* Font */
  ADD_NAME_VALUE(@"System Font", [[NSFont systemFontOfSize: [NSFont systemFontSize]] displayName]);
  ADD_NAME_VALUE(@"System Font Size", [[NSNumber numberWithFloat: [NSFont systemFontSize]] description]);
  ADD_NAME_VALUE(@"System Font Encoding", [[NSFont systemFontOfSize: [NSFont systemFontSize]] encodingScheme]);
  ADD_NAME_VALUE(@"System Bold Font", [[NSFont boldSystemFontOfSize: [NSFont systemFontSize]] displayName]);

  [root addChild: child];
  RELEASE(child);

  return self;
}

Now, it looks better:

圖形 14.10. Overview

Overview

在這個程式中, 當視窗關閉時程式還在. 在這裡使用 NSApp 的代理者來結束程式.

Controller.m

- (BOOL) applicationShouldTerminateAfterLastWindowClosed: (id) sender
{
  return YES;
}