タグ ‘Mac’ の記事

PocketMoneyViewer 0.14 Released

PocketMoneyViewer 0.14をリリースしました。変更点は、

  • 残高列の追加 (thanks エヌ氏)
  • ソートディスクリプタの保存

以上の2点です。ダウンロード

ご要望や苦情はコメントか@magical_eieiでお願いします。

ネットワーク経由でGrowlを制御

Growl-Demo-Notification-View.png

C言語を使ってネットワーク経由でGrowlを制御してみた。
Growl SDKのBindingsにはC言語が入ってないんだよなぁ…

ネットワーク経由でGrowlを使うには

  1. アプリケーション登録前にGrowlにネットワーク経由での通知を許可
  2. アプリケーションの登録
  3. 通知

という手順を踏む。

Continue reading ‘ネットワーク経由でGrowlを制御’ »

core-plot: プロットに色をつける

201004052135-2.png

前回までのlog(x)のグラフがつまらなかったので、今回からは散布図っぽい五芒星にしてみました。

CPColor

core-plotで色を表すクラスはCPColorです。CPColorはCGColorのラッパクラスで単純な構造です。また、NSColor同様のコンビニエンスコンストラクタを持っています。

@interface CPColor : NSObject <NSCopying, NSCoding> {
    @private
    CGColorRef cgColor;
}
@property (nonatomic, readonly, assign) CGColorRef cgColor;

/// @name Factory Methods
/// @{
+(CPColor *)clearColor;
+(CPColor *)whiteColor;
+(CPColor *)lightGrayColor;
+(CPColor *)grayColor;
()
@end

まずは、このCPColorとNSColorの相互変換クラス(CPColorTransformer)を作ります。IBのカラーウェルで直接CPColorを操作することを想定しているため、NSValueTransformerのサブクラスとしています。

@interface CPColorTransformer: NSValueTransformer
@end
@implementation CPColorTransformer
+ (Class)transformedValueClass
{
    return [NSColor class];
}

+ (BOOL)allowsReverseTransformation
{
    return YES;
}

- (id)transformedValue:(id)cpColor
{
    const CGFloat *components;

    // XXX
    if (!cpColor)
        return [NSColor blackColor];

    components = CGColorGetComponents([cpColor cgColor]);

    return [NSColor colorWithDeviceRed:components[0] green:components[1] blue:components[2] alpha:components[3]];
}

- (id)reverseTransformedValue:(id)nsColor
{
    NSColor *rgbColor;
    CGFloat red, green, blue, alpha;

    rgbColor = [nsColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
    [rgbColor getRed:&red green:&green blue:&blue alpha:&alpha];

    return [CPColor colorWithComponentRed:red green:green blue:blue alpha:alpha];
}@end

CPScatterPlot

CPScatterPlotクラス図

次に、プロットに色をつけます。今回使用しているプロットCPScatterPlotでは、メンバ変数のdataLineStyle.lineColorにCPColorを持っています。このメンバに対してIBのカラーウェルで直接バインディングします。値変換には前に作ったCPColorTransformerを指定します。
Controllerの_hostingViewからCPScatterPlotの間にはNSArrayが入っているため、直接キーパスでバインディングすることができません*1。 そこで、Controllerにアウトレット(_plot)を追加します。それと、プロットを再描画させるためのアクション(reloadData:)も追加します。

201004052119-2.png
@interface Controller: NSObject <CPScatterPlotDataSource>
{
    IBOutlet CPLayerHostingView *_hostingView;
    CPScatterPlot *_plot;
}

- (IBAction)reloadData:(id)sender;
@end

_plotの追加にしたがって、実装部も変更します。

- (void)awakeFromNib
{
    ()
    // 散布図を追加
    // データはdataSourceプロトコルで提供
    _plot = [[CPScatterPlot alloc] init];
    _plot.identifier = @"5 star plot";
    _plot.dataSource = self;
    [graph addPlot:_plot];
}

- (IBAction)reloadData:(id)sender
{
    [_plot reloadData];
}

注意点としては、色変更後はreloadDataもしくはsetDataNeedsReloadingを呼び出す必要があります。今回のサンプルでは、カラーウェルのアクションをControllerのreloadDataに接続することによって自動的にreloadDataしています。

CPLineStyle

CPColorを保持してるCPLineStyleでは線の太さ(lineWidth)も保持しています。lineColor同様にIBのスライダにバインディングしてやると線の太さを簡単に変えることができます。

ソースからをSimpleGraph-1.2.tar.gzに置いています。

  • *1: できる方法を知っている人は教えてください!

core-plot: パディングの調整

201003132124-1.png

前回のグラフはラベルが見切れていて見栄えがよくありませんでした。今回は、このラベルの見切れを修正します。

CPLayer-subclasses-t.png ラベルの見切れを防ぐには、パディング*1を適切に設定する必要があります*2。パディングはCPLayerで定義されてるプロパティですから、その派生クラスで使用することができます。参考として、CPLayerから派生したクラスを右図に示します。
パディングは、上下左右を独立して設定することができ、それぞれpaddingLeft, paddingTop, paddingRight, paddingBottom(以下、まとめてpadding*とします)という名前になっています。

@interface CPLayer : CALayer <CPResponder> {
@private
    CGFloat paddingLeft;
    CGFloat paddingTop;
    CGFloat paddingRight;
    CGFloat paddingBottom;
    (以下略)

今回は、下と左が切れてるので、paddingBottom, paddingLeftを調整します。コードは以下のようになります。

- (void)awakeFromNib
{
    // XYグラフを作成してビューにのせる
    CPXYGraph *graph;
    graph = [[[CPXYGraph alloc] initWithFrame:_hostingView.bounds] autorelease];
    _hostingView.hostedLayer = graph;

    // 下, 左パディングを設定
    graph.paddingBottom = 40;
    graph.paddingLeft = 40;
    (以下略)
SimpleGraphp-1.1-t.png

これだけではおもしろくないので、対話的にパディングを変更させてみました。スライダ, テキストフィールドのバインド先はコントローラの_hostingView.hostedLayer.padding*です。
ソースをSimpleGraph-1.1.tar.gzに置いています。

次回は、グラフ*3に色をつけます。

  • *1: CSSのパディングと同じです
  • *2: 多分、英語環境ではデフォルトのままでちゃんと表示できてるのでしょうが…
  • *3: 正確にはプロット

Hello core-plot

core-plot

core-plotの特徴

core-plotはCocoaで2Dグラフを書くためのフレームワークです。以下のような特徴があります。

  • iPhoneOSに対応
  • グラフの種類はXYグラフ, 棒グラフ, 円グラフ等。PlotExamples
  • CoreAnimation, CoreData, Cocoa Bindingsをサポート

core-plotの概要を把握するためにHighLevelDesignOverviewの図だけでも眺めておくことをお勧めします。グッと理解しやすくなります*1

Hell core-plot

グラフの仕様

201003132124-1.png

今回は簡単なグラフを書きます。グラフの仕様は、

  • XYグラフ
  • グラフの範囲はX, Y軸共に0-10
  • プロットする関数はy=log(x+1)
  • データの個数は10個
  • dataSourceプロトコルを使用
  • コードはできるだけ少なくする
  • 見た目の汚さは気にしない

としています。

Xcode初期設定

201003131906-1-150x150.png
  1. Xcodeを立ち上げて、新規のCocoa Applicationプロジェクトを作成します
  2. プロジェクトのLinked FrameworksにCorePlotフレームワークを追加します(右図)*2
  3. core-plotのインクルードファイルが見つからずにコンパイルエラーが発生した場合は「プロジェクト」->「プロジェクト設定を編集」->「ヘッダ検索パス」にcore-plotをインストールしたディレクトリを追加します。

Controllerクラスの設計

Controllerクラスの要点は以下の二つです。

  1. CPScatterPlotDataSourceプロトコルに適合する
  2. グラフを乗せるためのビュー(_hostingView)をもつ

コードは以下のようになります。

#import <Cocoa/Cocoa.h>
#import <CorePlot/CorePlot.h>

@interface Controller: NSObject <CPScatterPlotDataSource>
{
    // CPGraphを乗せるためのビュー
    IBOutlet CPLayerHostingView *_hostingView;
}
@end

画面設計

Controller.hをプロジェクトに追加したら、IBで画面設計します。

  1. Controllerをインスタンス化します*3
  2. ウィンドウにCPLayerHostingViewを貼り付けます*4
  3. Controllerの_hostingViewとCPLayerHostingViewをつなぎます

最終的にこんな感じになります。以上で画面設計は終わりです。

Controllerクラスの実装

最後にControllerクラスの実装をおこないます。概要は、

  1. グラフを作成し、_hostingViewに登録
  2. グラフの範囲を決める
  3. グラフにプロットを貼り付ける
  4. dataSourceの実装

という具合です。

#import "Controller.h"

@implementation Controller

- (void)awakeFromNib
{
    // XYグラフを作成してビューにのせる
    CPXYGraph *graph;
    graph = [[[CPXYGraph alloc] initWithFrame:_hostingView.bounds] autorelease];
    _hostingView.hostedLayer = graph;

    // グラフの範囲を決める
    CPXYPlotSpace *plotSpace;
    plotSpace = (CPXYPlotSpace *)graph.defaultPlotSpace;
    plotSpace.xRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0)
                                    length:CPDecimalFromFloat(10)];
    plotSpace.yRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0)
                                    length:CPDecimalFromFloat(10)];

    // 散布図を追加
    // データはdataSourceプロトコルで提供
    CPScatterPlot *logPlot;
    logPlot = [[[CPScatterPlot alloc] init] autorelease];
    logPlot.identifier = @"log plot";
    logPlot.dataSource = self;
    [graph addPlot:logPlot];
}

/*
 * dataSourceプロトコル
 */

// データの個数を返す
- (NSUInteger)numberOfRecordsForPlot:(CPPlot *)plot
{
    return 10;
}

// fieldEnum軸のindex番目のデータを返す
// y = log(x+1)
-(NSNumber *)numberForPlot:(CPPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{
    // X軸のデータの場合はindexをそのまま返す
    if (fieldEnum == CPScatterPlotFieldX)
        return [NSNumber numberWithInt:index];

    // Y軸の場合はlog(index+1)を返す
    return [NSNumber numberWithFloat:log(index+1)];
}
@end

dataSourceプロトコル

dataSourceプロトコルはNSTableView等のdataSourceプロトコルと似た作りになっています。今回使用したのは、

numberOfRecordsForPlot:
(必須)と
- (NSNumber *)numberForPlot:field:recordIndex:
(オプション)です。

手順は、

  1. numberOfRecordsForPlot:
    でデータの個数を返します
  2. numberForPlot:field:recordIndex:
    でfield軸のrecordIndex番目のデータを返します

図にするとこんな感じです。

今回のソースコードを SimpleGraph-1.0.tar.gzに置いています。core-plotフレームワークも一緒についてるので、すぐにコンパイルできます。

次回は少しだけ見た目に手を入れたいと思います。

  • *1: もちろん説明を読むのがいいけど
  • *2: 「Linxed Frameworks」で右クリック->「追加」->「既存のフレームワーク」->「その他を追加...」でCorePlot.frameworkを選択
  • *3: LibraryウィンドウからObjectを選択し、MainMenu.xibにドロップ。インスペクタのClass IdentifyでClassをControllerにする
  • *4: LibraryウィンドウからCustom ViewをWindowにドロップ。インスペクタのClass IdentifyでClassをCPLayerHostingViewにする

core-plotをインストール

core-plot

core-plotという素敵なフレームワークを発見した。でも、日本語の解説がない。というわけで、勝手に解説してみようと思う。まずは、core-plotのインストールから。

Mercurial

現時点でcore-plotはバイナリやtarballで配布されておらず、ソースコードを入手してから自分でコンパイルする必要がある。ソースコードはGoogle Codeから入手することができるが、Mercurialが必要となる。Mac OS XではMercurialは標準でインストールされていないため、自分でコンパイルするかMacPortsを利用してインストールする。

インストール

  1. まずはcore-plotのソースコードを持ってくる。ターミナルで以下のコマンドを入力。
    $ hg clone https://core-plot.googlecode.com/hg/ core-plot
  2. core-plot/framework/CorePlot.xcodeproj
    をクリックしてXcodeを立ち上げる
  3. アクティブな構成をReleaseにする(任意)
  4. ビルド
  5. core-plot/framework/CorePlot.xcodeproj/{Release,Debug}/CorePlot.framework
    を適当なディレクトリにコピーする*1

これでインストール完了です。次回は簡単なグラフを書きます。

  • *1: うちでは
    ~/Library/Frameworks
    にしてます

Blocksと入れ子関数

Grand Central Dispatch試す前にボチボチとBlocksを調べ中。で、遊んでて面白いことに気付いた。以下のプログラムではブロックと入れ子関数は同じ振る舞いをするだろうと思っていたのだが、そうではなかった。(コンパイルするには-fnested-functionsオプションつけてください)

int main()
{
    int n = 10;
    int (^f)() = ^{ return n; };
    int g()       { return n; };

    n = 100;
    printf("f() = %d\n", f());
    printf("g() = %d\n", g());

    return 0;
}

予想では

f()
,
g()
共に100を返すだろうと思っていた。Lisp, Python, JavaScript…ではそう動くしね。しかし、
f()
10を返してきた。どうやら、ブロック内部の変数は定義した時の値を持っているようだ。

予想どうり動かそうとすると、

n
__block
をつける必要がある。

int main()
{
    __block int n = 10;
    int (^f)() = ^{ return n; };
    int g()       { return n; };

    n = 100;
    printf("f() = %d\n", f());
    printf("g() = %d\n", g());

    return 0;
}

逆に言えば、

__block
がついてない変数はブロックの中と外で独立してるので、煩わしい排他処理が不必要ということになる(ブロック外の変数は不変なのでブロック内でタイミングを考慮する必要はない)。ロジックが簡単になるので、その分スループットも上がるよね。たぶん、これを考慮してこんな実装にしたんだと思うけど、考えた人は賢いなぁ。

それから、ブロックの実体はCoreFoundationのオブジェクトのようでした。

$ gcc -g -O0 -fnested-functions z.c
$ gdb a.out
(略)
(gdb) n
4       int (^f)() = ^{ return n; };
(gdb) n
7       n = 100;
(gdb) p f
$1 = (struct __block_literal_1 *) 0x7fff5fbff2d0
(gdb) p *f
$2 = {
  __isa = 0x7fff703c5300,
  __flags = 536870912,
  __reserved = 0,
  __FuncPtr = 0x100000ea2,
  __descriptor = 0x100001090,
  n = 10
}
(gdb) disassemble f->__FuncPtr
Dump of assembler code for function __main_block_invoke_1:
0x0000000100000ea2 <__main_block_invoke_1+0>:   push   %rbp
0x0000000100000ea3 <__main_block_invoke_1+1>:   mov    %rsp,%rbp
0x0000000100000ea6 <__main_block_invoke_1+4>:   mov    %rdi,-0x18(%rbp)
0x0000000100000eaa <__main_block_invoke_1+8>:   mov    -0x18(%rbp),%rax
0x0000000100000eae <__main_block_invoke_1+12>:  mov    0x20(%rax),%eax
0x0000000100000eb1 <__main_block_invoke_1+15>:  mov    %eax,-0x4(%rbp)
0x0000000100000eb4 <__main_block_invoke_1+18>:  mov    -0x4(%rbp),%eax
0x0000000100000eb7 <__main_block_invoke_1+21>:  leaveq
0x0000000100000eb8 <__main_block_invoke_1+22>:  retq  
End of assembler dump.
(gdb)