TiltTest.c
Windowsプログラミングにはなじみが無いので、関数がどこから来たものか調べながら読み下してみる。
#include <windows.h>   // ... Microsoft SDKs/Windows/v7.0/include/windows.h
#include <math.h>      // ... Microsoft Visual Studio 10.0/VC/include/math.h
#include <string.h>    // ... 上に同じ
#include "wintab.h"    // NOTE: get from wactab header package (TiltTest.zipに同梱)
#define PACKETDATA     (PK_X | PK_Y | PK_BUTTONS | PK_NORMAL_PRESSURE | \
                        PK_ORIENTATION | PK_CURSOR)
                       // PK_xxはwintab.h内で定義
#define PACKETMODE     0
#include "pktdef.h"    // NOTE: get from wactab header package (TiltTest.zipに同梱)
#include "msgpack.h"   // (TiltTest.zipに同梱)
#include "Utils.h"     // (TiltTest.zipに同梱)
#include "tilttest.h"  // (TiltTest.zipに同梱)
/* converts FIX32 to double */
#define FIX_DOUBLE(x)   ((double)(INT(x))+((double)FRAC(x)/65536))
#define pi 3.14159265359
ここまでは特に難しいことはない。
#ifdef WIN32 #define MoveTo(h,x,y) MoveToEx(h,x,y,NULL) #endif
MoveToEx は Microsoft SDKs/Windows/v7.0A/Include/WinGDI.h の4520行にプロトタイプ宣言?がある。
char* gpszProgramName = "TiltTest"; HANDLE hInst; /* Handle for instance */ HCTX hTab = NULL; /* Handle for Tablet Context */ POINT ptNew; /* XY value storage */ UINT prsNew; /* Pressure value storage */ UINT curNew; /* Cursor number storage */ ORIENTATION ortNew; /* Tilt value storage */ RECT rcClient; /* Size of current Client */ RECT rcInfoTilt; /* Size of tilt info box */ RECT rcInfoName; /* Size of cursor name box */ RECT rcInfoGen; /* Size of testing box */ RECT rcDraw; /* Size of draw area */ double aziFactor = 1; /* Azimuth factor */ double altFactor = 1; /* Altitude factor */ double altAdjust = 1; /* Altitude zero adjust */ BOOL tilt_support = TRUE; /* Is tilt supported */
以上はグローバル変数の定義。
ptNew以後は、タブレットから来たデータを保持する構造体を指すポインタらしい。
上から順に、XY座標、筆圧、カーソル番号(ペン先か消しゴムかひょっとするとサイドスイッチの状態も)、ペンの傾き角度、の様子。
rcClient以後は、情報をウインドウ上に表示するエリアのための構造体らしい。
aziFactor ... 方位角についての何らかの係数
altFactor ... 加速度についての何らかの係数
altAdjust ... 加速度についての何らかの係数
最後に、ペンの傾きをサポートしてないタブレットではflaseになるBOOL型変数か。
ptNew以後は、タブレットから来たデータを保持する構造体を指すポインタらしい。
上から順に、XY座標、筆圧、カーソル番号(ペン先か消しゴムかひょっとするとサイドスイッチの状態も)、ペンの傾き角度、の様子。
rcClient以後は、情報をウインドウ上に表示するエリアのための構造体らしい。
aziFactor ... 方位角についての何らかの係数
altFactor ... 加速度についての何らかの係数
altAdjust ... 加速度についての何らかの係数
最後に、ペンの傾きをサポートしてないタブレットではflaseになるBOOL型変数か。
- ここでカーソル番号curNewの内容として、ペンのペン先か消しゴム側かの情報に加えて、サイドスイッチの情報や、モノによってはタブレットに仕込まれたスイッチの情報を返しているかもしれないことに注意しておく。
- また、タブレットが複数接続されている場合には hInst や hTab の内容が変わっているかもしれない。
続いてプログラム本体部分が来る。
int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow) HINSTANCE hInstance; HINSTANCE hPrevInstance; LPSTR lpCmdLine; int nCmdShow;
この構文だとVC++2010のデフォルトでは、インテリセンス機能による警告が出る。警告の出ない構文に書き直すと、例えば次のようになる。
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
WinMain()の中身はいたってシンプル。
{
	MSG msg;
	if (!hPrevInstance)
		if (!InitApplication(hInstance))
			return (FALSE);
	/* Perform initializations that apply to a specific instance */
	if (!InitInstance(hInstance, nCmdShow))
		return (FALSE);
	/* Acquire and dispatch messages until a WM_QUIT message is received. */
	while (GetMessage(&msg, NULL, 0, 0)) 
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	// Return Wintab resources.
	Cleanup();
	return (msg.wParam);
}
WinMain() は Windows でのウインドウアプリケーションの主要関数で、C言語の main() に相当するものとのこと。Web を調べると、どのサンプルコードでもこの部分はほとんど違いがない。この関数が実行されたら、あとはマウスやキーボードや、TiltTest の場合はタブレットのイベント割り込みを待つことになる。
その割り込み処理をフックして、アプリケーションのコードを記述することになる。
その割り込み処理をフックして、アプリケーションのコードを記述することになる。
InitApplication()
BOOL InitApplication(HINSTANCE  hInstance)
// BOOL InitApplication(hInstance)
// HANDLE  hInstance;  // 呼び出し側が HINSTANCE でありこの文おそらく間違いなので変更する。TILTTEST.Hも。
{
	WNDCLASS wc;
	/* Fill in window class structure with parameters that describe the */
	/* main window. */
	wc.style = 0;
	wc.lpfnWndProc = MainWndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
	wc.lpszMenuName =  "TiltTestMenu";
	wc.lpszClassName = "TiltTestWClass";
	/* Register the window class and return success/failure code. */
	return (RegisterClass(&wc));
}
WNDCLASS は Microsoft SDKs/Windows/v7.0A/Include/WinUser.h で 構造体 tagWNDCLASSA で定義されている。アプリケーションウインドウの雛形が作成されているのかな。
InitInstance()
長いので調査結果や変更点はソースコードにコメントの形で埋め込んだ。
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
// 第一引数の型は呼び出し側が HINSTANCE なのでそれにあわせた。TILTTEST.H も書き直した。
// HANDLE  hInstance;
// int     nCmdShow;
{
	HWND            hWnd;             /* Handle for window */
	HDC             hDC;              /* Handle for Device Context */
	TEXTMETRIC      textmetric;       /* Structure for font info */
	int             nLineH;           /* Holds the text height */
	int             Xinch, Yinch;     /* Holds the number of pixels per inch */
	int             Hres, Vres;       /* Holds the screen resolution */
	char            WName[50];        /* String to hold window name */
	struct          tagAXIS TpOri[3]; /* The capabilities of tilt */
	double          tpvar;            /* A temp for converting fix to double */
/* Save the instance handle in static variable, which will be used in  */
/* many subsequence calls from this application to Windows.            */
hInst = hInstance;
hInst はこのソースのはじめで定義したグローバル変数。
if ( !LoadWintab( ) )
{
	ShowError( "Wintab not available" );
	return FALSE;
}
LoadWintab()の戻り値でタブレットサービスの有無がわかるようだ。
/* check if WinTab available. */
if (!gpWTInfoA(0, 0, NULL)) {
	MessageBox(NULL, "WinTab Services Not Available.", gpszProgramName, 
		   MB_OK | MB_ICONHAND);
	return FALSE;
}
gpWTInfoA()の引数でタブレットに関するいろいろな情報が得られる模様。
http://www.wacomeng.com/windows/docs/Wintab_v140.htm の WTInfo を参照すると、引数は
http://www.wacomeng.com/windows/docs/Wintab_v140.htm の WTInfo を参照すると、引数は
- カテゴリー: 情報のカテゴリ(種類)を整数で指定
- インデックス: カテゴリ内のどの情報かを整数で指定
- 戻り値を入れるバッファへのポインタ
となっている。(0,0,NULL)の指定だと、必要なバッファサイズ(一番大きな情報を返すカテゴリのために必要なバッファのサイズ)を返す。
タブレットが接続されていないなど利用できない状態の場合は0を返す。上記のコードは0かどうかを見ている。
タブレットが接続されていないなど利用できない状態の場合は0を返す。上記のコードは0かどうかを見ている。
	/* check if WACOM available. */
    gpWTInfoA(WTI_DEVICES, DVC_NAME, WName);
    if (strncmp(WName,"WACOM",5)) {
		MessageBox(NULL, "Wacom Tablet Not Installed.", gpszProgramName, 
		    	   MB_OK | MB_ICONHAND);
//      return FALSE;
    }
上記のコードは、ワコム社のタブレットが接続されているかどうかを、帰ってきた値の中にWACOMの文字列があるかどうかでチェックしている。
WTI_DEVICES と DVC_NAME は .h の中で定義されているのだろう。この組み合わせでデバイスの名前を返す模様。
組み合わせる値と返る情報の表の翻訳版があったのでありがたく参照させていただく(外部参照)。
WTI_DEVICES と DVC_NAME は .h の中で定義されているのだろう。この組み合わせでデバイスの名前を返す模様。
組み合わせる値と返る情報の表の翻訳版があったのでありがたく参照させていただく(外部参照)。
	/* get info about tilt */
	tilt_support = gpWTInfoA(WTI_DEVICES,DVC_ORIENTATION,&TpOri);
	if (tilt_support) {
		/* does the tablet support azimuth and altitude */
		if (TpOri[0].axResolution && TpOri[1].axResolution) {
			/* convert azimuth resulution to double */
			tpvar = FIX_DOUBLE(TpOri[0].axResolution);
			/* convert from resolution to radians */
			aziFactor = tpvar/(2*pi);  
			
			/* convert altitude resolution to double */
			tpvar = FIX_DOUBLE(TpOri[1].axResolution);
			/* scale to arbitrary value to get decent line length */ 
			altFactor = tpvar/1000; 
			 /* adjust for maximum value at vertical */
			altAdjust = (double)TpOri[1].axMax/altFactor;
		}
		else {  /* no so dont do tilt stuff */
			tilt_support = FALSE;
		}
	}
上記はペンの傾き情報が利用できるかどうかをチェックしている模様。
/* Create a main window for this application instance. */ wsprintf(WName, "TiltTest:%x", hInst); hWnd = CreateWindow( "TiltTestWClass", WName, WS_OVERLAPPEDWINDOW, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); /* If window could not be created, return "failure" */ if (!hWnd) return (FALSE);
上記はアプリケーションウインドウを表示する部分らしい。
/* Get Device Context and setup a rects to write packet info */ hDC = GetDC(hWnd); if (!hDC) return FALSE; // 以下のブロックは // ウインドウにテキストを書くための、フォントのサイズなどのパラメータを取得している。 GetTextMetrics(hDC, &textmetric); nLineH = textmetric.tmExternalLeading + textmetric.tmHeight; Xinch = GetDeviceCaps(hDC, LOGPIXELSX); Yinch = GetDeviceCaps(hDC, LOGPIXELSY); Hres = GetDeviceCaps(hDC, HORZRES); Vres = GetDeviceCaps(hDC, VERTRES); ReleaseDC(hWnd, hDC);
GetClientRect(hWnd, &rcClient); rcInfoTilt = rcClient; rcInfoTilt.left = Xinch / 8; rcInfoTilt.top = Yinch / 8; rcInfoTilt.bottom = rcInfoTilt.top + nLineH; rcInfoName = rcInfoTilt; rcInfoName.top += nLineH; rcInfoName.bottom += nLineH; rcInfoGen = rcInfoName; rcInfoGen.top += nLineH; rcInfoGen.bottom += nLineH; rcDraw = rcInfoGen; rcDraw.left = 0; rcDraw.top += nLineH; rcDraw.bottom = rcClient.bottom;
以下、書きかけ。
/* Make the window visible; update its client area; and return "success" */ ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return (TRUE);
}
これを実行するとウインドウが表示され、ペンのタブレット上の位置が十字カーソルで表示され、十字カーソルを中心にして筆圧に従った大きさの円が描画される。カーソルの座標はタブレット面の座標に対応しており、タブレットドライバによるマルチモニタの割付などは無視している。
またウインドウがアクティブの間、マウスカーソルとタブレットの十字カーソルは別物として動作する。つまりマウスカーソルはマウスで、十字カーソルはタブレットペンで動く。
さらにタブ1=画面1、タブ2=画面2と割り付けてあったとき、ウインドウをどちらの画面に表示させてもタブ面の絶対座標が画面上の絶対座標に対応するかのように十字カーソルは動く。
                            
またウインドウがアクティブの間、マウスカーソルとタブレットの十字カーソルは別物として動作する。つまりマウスカーソルはマウスで、十字カーソルはタブレットペンで動く。
さらにタブ1=画面1、タブ2=画面2と割り付けてあったとき、ウインドウをどちらの画面に表示させてもタブ面の絶対座標が画面上の絶対座標に対応するかのように十字カーソルは動く。
