スクリーントップの1ラインをminu切り替えのスイッチにする

メニューバー右側のアイコンメニューをなるべく多く表示することを目的としたアプリケーションminuをアクティブにするには、現状はコマンド + タブやDockでアプリケーション切り替えするしかない。これを改善して、メニューバーの一番上の1水平ラインをクリックすることで、minuに切り替えられるようにしようと思う。大まかな処理の流れは以下のような感じ。

  • メニューバーよりも上に重なる透明ウィンドウを用意する。
  • 上記ウィンドウに水平ラインを一本引く。
  • マウスイベントを処理する。

早速、作業開始!

透明ウィンドウを作る

透明ウィンドウは背景がガラスのように透明で、文字や図形が描画された部分以外は、その下の情報がすべて透き通って見える。感覚的にはデスクトップ上で、まるでフォトショップのレイヤーを手に入れたような操作が可能で、とても応用力のある技だと思う。以下のURLにアップルのサンプルコードが置かれている。

そして、上記サンプルコードを日本語で、これ以上無いというほど親切丁寧に解説してくれているのが、Cocoaはやっぱりの以下のページ。

上記サンプルコードと解説を参考にすると、まず必要になるのが透明ウィンドウを定義するためのNSWindowのサブクラス。ClearWindowとして以下のように定義してみた。

// ---------- ファイル名: CrearWindow.h ----------
#import <Cocoa/Cocoa.h>


@interface ClearWindow : NSWindow {

}

@end
// ---------- ファイル名: CrearWindow.m ----------
#import "ClearWindow.h"


@implementation ClearWindow

static float MENU_BAR_HEIGHT = 22;

// ウィンドウを初期化して生成
- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
    NSRect mainScreenFrame = [[NSScreen mainScreen] frame];
    NSRect areaFrame = NSMakeRect(mainScreenFrame.origin.x, 
				  mainScreenFrame.size.height - MENU_BAR_HEIGHT, 
				  mainScreenFrame.size.width, 
				  MENU_BAR_HEIGHT);
    //ウィンドウの初期化(NSBorderlessWindowMaskによってメニューバーを超えた移動が可能になる)
    id win = [super initWithContentRect:areaFrame styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag];
    
    //ウィンドウが描画される上下レベル(レイヤーレベル)を設定(メニューバーよりも上のレベルに)
    [win setLevel: NSPopUpMenuWindowLevel];
    
    //ウィンドウの背景色を透明色に設定
    [win setBackgroundColor: [NSColor clearColor]];
    
    //ウィンドウは不透明でない(つまり、透明である)
    [win setOpaque:NO];
    
    //ウィンドウの透過率を設定(不透明1.0〜完全透明0.0)
    [win setAlphaValue:0.0];
    
    //ウィンドウの影を表示しない
    [win setHasShadow: NO];
    
    //ウィンドウをすべてのSpaceで表示する(Spaces対応のため)
    [win setCanBeVisibleOnAllSpaces: YES];
	
    return win;
}

@end
  • ウィンドウの背景色を透明にしたら([win setBackgroundColor: [NSColor clearColor]];)、ウィンドウの不透明さも常にNOにしておくべき([win setOpaque:NO];)。それにしても、なぜ不透明さの設定項目があるのか...。
  • 上記二つの設定とは無関係に、ウィンドウの透過率を設定することができる([win setAlphaValue:0.0];)。1から0の範囲で、1は全く透過しない不透明、0だと完全に透明で見えなくなる。

イベント処理をする水平ラインを作る

  • 今のところ、水平ラインをクリックしたらminuをアクティブにするだけなので、特別なイベント処理は必要ないが、この後の発展を考えてNSViewのサブクラスを作っておいた。
  • 水平ラインの色には、GUI部品にキーボードでアクセスする時のフォーカスリングの色を指定した。([[NSColor keyboardFocusIndicatorColor] set];//自分の環境では青っぽい水色になる。)
// ---------- ファイル名: EventView.h ----------
#import <Cocoa/Cocoa.h>


@interface EventView : NSView {

}

@end
// ---------- ファイル名: EventView.m ----------
#import "EventView.h"


@implementation EventView

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
    }
    return self;
}

- (void)drawRect:(NSRect)rect {
    //一番上に水平ラインを描画
    NSRect aFrame = [self frame];
    NSPoint point0 = NSMakePoint(aFrame.origin.x, NSHeight(aFrame));
    NSPoint point1 = NSMakePoint(NSWidth(aFrame), NSHeight(aFrame));

    [[NSColor keyboardFocusIndicatorColor] set];
    [NSBezierPath setDefaultLineWidth:2];
    [NSBezierPath strokeLineFromPoint:point0 toPoint:point1];
}

@end

設計したクラスをInterface Builderから利用する

ClearWindowの設定
  • 上記のXcodeでの作業を一旦保存した。
  • Interface BuilderのMainMenu.nilウィンドウを開いて、その中のWindowアイコンを選択した状態にしておく。
    • 「コマンド + 6」のキーボードショットカットを実行して、「Identify」の設定。
      • インスペクタ ウィンドウの「Class」項目にXcodeで設計した「ClearWindow」を選択しておく。
    • 「コマンド + 1」のキーボードショットカットを実行して、「Attributes」の設定。
EventViewの設定
  • MainMenu.nilウィンドウのWindowアイコンをダブルクリックして、(GUI設計用の)ウィンドウを開く。
  • キーボードショットカット「コマンド + シフト + L」を実行して、ライブラリ ウィンドウを開く。
  • 下の方の検索ボックスで「view」と入力してみる。
  • 検索結果から「CustomView」を探して、(GUI設計用の)ウィンドウにドラッグ&ドロップ。
  • 「Identify」インスペクタ ウィンドウの「Class」項目にXcodeで設計した「EventView」を選択。
  • 「コマンド + 1」のキーボードショットカットを実行して、「size」インスペクタ ウィンドウを開く。
    • 「EventView」の高さはメニューバーと同じ22を指定。
    • 「EventView」を上に寄せて、横幅はウィンドウと同じ幅で配置した。
    • 「EventView」のAutosizingで、上左右の位置固定、幅のみ伸縮自在の設定にしておく。


ここまで設定して、Interface Builderで一旦保存した。Xcodeに戻って、「ビルドして進行」を実行すると、スクリーントップに水色の水平ラインが引かれた!クリッックすれば、アプリケーションminuがアクティブになって、メニューバーのフリースペースを最大にしてくれる。
でも、文字メニューを操作したい時に間違ってクリックしてしまうと、不要なアプリケーションminuに切り替わってしまいガックリ...。今後の改善点はこの辺になりそうだ。