OneKey3.exe for Bricscad V17~V19 Ver.1.24b 2018/12/02~12

OneKey.exe は、BcadMoseMove に移行しました。

・ 2018/12/12 一時Oスナップに「図心 gce」。(OneKey3 Ver.1.24b)
・ 2018/12/08 エラー回避に Bricscad のイベント通知を追加。(OneKey3 Ver.1.24a)

■OneKey2.exe との違い
・OneKey2 の対象は、複数起動された Bricscad のうちアクティブな Bricscad でしたが、最初に見つかった 1つの Bricscad のみにしました。
・OneKey2 では、LISP の読み込みメッセージ等でエラーになることがありましたが、図面 OPEN 中の 0.7 秒程度は何もしないことで、これを回避しました。

■概要
 3つの機能があります。
 ・IME 自動オフ (IME Off)
 ・マウス移動による [Enter] 発行 (OneKey)
 ・座標入力時、[Ctrl] キーによるスナップモード切替(Ver.1.21)

■スクリーンショット
 

■IME Off
 コマンドラインの文字列が ": " だけの時、IME をオフにします。
 ※不要の場合は、「IME Off:Auto」のチェックを外してください。

■OneKey
※不要の場合は、「OneKey : Auto」のチェックを外してください。
・コマンド入力中:
  マウス右上方向で [Enter] キーを発行します。
  例えば、コマンドラインに "z" を入力しマウスを右上に移動すると、ZOOM コマンドが実行されます。
  自動入力候補(コートコンプリート)を使うため、マウス右上以外は無効としています。
  文字数の制限はありません。
・コマンドオプション入力中:
  "["、"]" の文字列がある、または "点"、"ポイント"、"コーナ"、"中心"の文字列があり、"@"、数字入力でない時のみ、マウス方向制限なしで [Enter] キーを発行します。
  また、下記の文字(大/小文字区別なし)の場合は、一時Oスナップに書き換えます。
   M, MI, MD, MM : mid (中点)
   C, CE, CN, CC : cen (中心)
   Q, QU, QA, QQ : qua (四半円点)
   I, IT, II   : int (交点)
   E, EN, ED, EE : end(端点)
   T, TA, TN, TT : tan (接線)
   N, NE, NA   : nea (近接点)
   P, PE, PR, PP : per (垂線)
   S, IN, IS   : ins (挿入基点)
   O, D, ND     : nod (点)
   NN, NO   : non (解除)
   V, M2, MT, MP, MMM : m2p (2点間中点)
   EX, ET   : ext (延長)
   F, FR, FM   : from (基点設定)
   A, AP     : app (仮想交点)
   PA     : par (平行)
   X, XX     : [ESC]キー
   G, GC  :gce (図心) (1.24bより)
  ※コマンドオプションが優先されます。

■[Ctrl]キーによるスナップモード変更
 座標入力中に [Ctrl]キーを押すと、スナップモードが反転します。

■起動と終了
 起動すると、スクリーンの右下にフォームが表示されます。
 Bricscad の サポートフォルダに、on_start.lsp をメモ帳などで作成(すでにある場合は、下記一行を追加)すると、自動で起動されます。
 (startapp "C:\\xxxx\\yyyy\\OneKey3.exe")
 フォルダ(パス)の区切り \\ は¥¥です。
 Bricscad が終了すると、追従して終了します。

■マウス移動量の変更
 OneKey3.exe が起動していない時に、OneKey3.exe と同じフォルダの OneKey3.ini をメモ帳などで編集してください。
 MouseMove=20 ←この数値を変更

■以前 (AutoCAD 用) のバージョンとの違い
・マウス移動で、コマンド発行。名前のとおり OneKey でコマンドオプションを発行するツールでしたが、かなりクセがあり動きが分かりにくいため、
 ほぼマウス移動で [Enter] キーを発行する単純なものに変更しました。
・コマンドラインオプションのリスト表示の機能は、CAD 本体に実装されたため削除しました。
・[Shift] キーによる直交モード反転は、CAD 本体に実装されたため削除しました。
・[Ctrl] キーによるスナップモード反転は、削除しました。
・「アイコントレイに格納」は取りやめました。
・IME の強制オフ(IME Off) を統合しました。

■著作権、免責事項等
・本ツールの著作権は、作者 f.izawa が所有し、これを主張します。
・本ツールを使用したことによる事故、損害等の一切について作者はその責を負いません。

■開発環境
・Delphi 10.2.3 Tokyo Community Edition
・Bricscad V17 Pro, V19 Pro
・Windows 10 64bit

■作者連絡先
 e-mail: f.izawa@dream.com
 URL: http://www.izawa-web.com/

■ダウンロード

 ・OneKey3.zip (Ver.1.24b EXE 本体 + ReadMe)
  OneKey2、Onekey3 共、動作不安定のため、ダウンロードは中止にしました。


■Delphi ソースコード (Ver.1.24a)
 ※キーフックのコードは、Mr.XRAY 氏のサンプルコードを使用しています。
 ※ BcadCtrl19.pas は不要になりました。
 ※ BricscadApp_TLB, BricscadDb_TLB は、[コンポーネント] - [コンポーネントのインポート...] -「タイプライブラリの取り込み」で作成します。

// 2018/12/09

unit OneKey3Unit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, ShellAPI, Vcl.ExtCtrls,
  Vcl.StdCtrls, IMM, System.UITypes, IniFiles, Menus,
  ComObj, ActiveX, BricscadApp_TLB, BricscadDb_TLB;

Type
  // パラメータ付きのイベントをタイプ別に用意
  TParamWideStringEvent  = procedure(ASender: TObject; const Param: WideString) of object;
  TParamVarWordBoolEvent = procedure(ASender: TObject; var Param: WordBool) of object;
type
  TAcadApplicationEventsSink = class(TInterfacedObject, IUnknown, IDispatch)
  private
    FOwner : TObject;

    FOnNewDrawing    : TNotifyEvent;
    FOnBeginOpen : TParamWideStringEvent;
    FOnEndOpen : TParamWideStringEvent;
    FOnUnknownEvent : TNotifyEvent;
    FOnBeginQuit  : TParamVarWordBoolEvent;
  protected
  {
  イベントシンククラスのインスタンスが用意できたら,このイベントシンクの存在を
  サーバーに知らせます。
  これで,サーバーはイベントシンクを呼び出せるようになります。
  サーバーにイベントシンクの存在を知らせるには,グローバル手続き InterfaceConnect
  を呼び出し,以下を渡します。
    ・イベントを生成するサーバーとのインターフェース
    ・イベントシンクが処理するイベントインターフェースの GUID
    ・イベントシンクの IUnknown インターフェース
    ・サーバーとイベントシンクの接続を表す Longint 値を受け取る変数
   }
    { IUnknown }
    function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    { IDispatch }
    function GetTypeInfoCount(out Count: Integer): HRESULT; virtual; stdcall;
    function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HRESULT; virtual; stdcall;
    function GetIDsOfNames(const IID: TGUID; Names: Pointer;
      NameCount, LocaleID: Integer; DispIDs: Pointer): HRESULT; virtual; stdcall;
    function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
      Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HRESULT; virtual; stdcall;
  public
    constructor Create(Owner:TObject);
    property OnNewDrawing    : TNotifyEvent read FOnNewDrawing write FOnNewDrawing;
    property OnBeginOpen : TParamWideStringEvent read FOnBeginOpen write FOnBeginOpen;
    property OnEndOpen : TParamWideStringEvent read FOnEndOpen write FOnEndOpen;
    property OnBeginQuit : TParamVarWordBoolEvent read FOnBeginQuit write FOnBeginQuit;
    property OnUnknownEvent : TNotifyEvent read FOnUnknownEvent write FOnUnknownEvent;
  end;

type
  TOneKeyForm = class(TForm)
    Timer1: TTimer;
    CheckBox1: TCheckBox;
    Shape1: TShape;
    Label1: TLabel;
    CheckBox2: TCheckBox;
    Timer2: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure Timer2Timer(Sender: TObject);
  private
    { Private 宣言 }

    FAcadApplication : IAcadApplication;
    EventChanel : TAcadApplicationEventsSink;
    CPC: IConnectionPointContainer;
    CP : IConnectionPoint;
    cookie : Integer;
    procedure BcadBeginOpen(Sender: TObject;const FileName: WideString);
    procedure BcadEndOpen(Sender: TObject;const FileName: WideString);
    procedure BcadNewDrawing(Sender: TObject);
    procedure BcadBeginQuit(Sender: TObject; var Cancel: WordBool);
  public
    { Public 宣言 }
  protected
    procedure WMApp110(var Message: TMessage); message WM_APP + 110;
  end;

type
  LPKBDLLHOOKSTRUCT = ^KBDLLHOOKSTRUCT;
  tagKBDLLHOOKSTRUCT = record
    vkCode      : DWORD;
    scanCode    : DWORD;
    flags       : DWORD;
    time        : DWORD;
    dwExtraInfo : ULONG_PTR;
  end;
  KBDLLHOOKSTRUCT  = tagKBDLLHOOKSTRUCT;
  PKbDllHookStruct = LPKBDLLHOOKSTRUCT;

var
  OneKeyForm: TOneKeyForm;
  KeyHookHandle   : HHOOK;
  TargetWnd       : HWND;
  BcadHwnd : HWND;
  BcadCmdHwnd: HWND;

  cmdstr : string;
  cmdopt : string;
  TmCnt : integer;
  TmFlag : boolean;

  bcadCaption, oldCaption : string;
  // マウス座標を保持
  mousex, mousey : integer;
  // マウス移動判断の閾値
  mousexy : integer;
  // 前回のコマンドライン文字列
  cmdold : string;
  // Bcad が起動中ON
  bcadflag : boolean;
  tmbusy : boolean;
  pt : TPoint;
  OneKeySleep : boolean;

  function makeCmdOptionList(const cmds: string): string;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  キーフックのコールバック関数
//-----------------------------------------------------------------------------
function LowLevelKeyProc(Code: Integer; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
var
  Lkbdll    : PKBDLLHOOKSTRUCT;
begin
  if Code < 0 then begin
    Result := CallNextHookEx(KeyHookHandle, Code, wPar, lPar);
    exit;
  end;

  Lkbdll := PKBDLLHOOKSTRUCT(lPar);
  if Code = HC_ACTION then begin
    if wPar = WM_KEYDOWN then begin
      PostMessage(OneKeyForm.Handle, WM_APP + 110, wPar, Lkbdll.vkCode);
    end
    else if wPar = WM_KEYUP then begin
      PostMessage(OneKeyForm.Handle, WM_APP + 110, wPar, Lkbdll.vkCode);
    end
  end;
  Result := CallNextHookEx(KeyHookHandle, Code, wPar, lPar);
end;

procedure BcadSendKeys(const keystr: string);
{ Bricscad をアクティブにして、Keyを押す}
var
  i : integer;
  s: string;
begin
  SetForegroundWindow(BcadHwnd);
  s := UpperCase(keystr);
  for i := 1 to Length(s) do begin
    keybd_event(Byte(s[i]), 0, 0, 0);
    keybd_event(Byte(s[i]), 0, KEYEVENTF_KEYUP, 0);
    Sleep(1);
  end;
end;

//-----------------------------------------------------------------------------
//  フックしたキーボード情報を受取る
//  KEYDOWNで送られてくる
//-----------------------------------------------------------------------------

procedure TOneKeyForm.WMApp110(var Message: TMessage);
var
  LKey     : WORD;
  LMsgKind : WORD;
  LhTarget : HWND;
  LMsgStr  : String;
begin
  LKey     := Message.LParam;
  LMsgKind := Message.WParam;
  if LMsgKind = $100 then LMsgStr := 'KeyDown'
  else if LMsgKind = $101 then LMsgStr := 'KeyUp'
  else LMsgStr := 'Key???';
  //最前面のウィンドウのハンドルがTargetWndと同じだったら処理
  //この条件を削除すれば,全てのウィンドウでのキー操作を検出することになる
  LhTarget := GetForegroundWindow;
  if (LhTarget = TargetWnd) and IsWindowEnabled(LhTarget) then begin
    if ShortCutToText(LKey) = 'Ctrl' then begin
      if (cmdstr = '') or
        ((cmdstr <> '') and ((Pos('点', cmdstr) > 0) or (Pos('ポイント', cmdstr) > 0) or
        (cmdstr.IndexOf('コーナ') >= 0) or (cmdstr.IndexOf('中心') >= 0))) then begin
        keybd_event(VK_F9, 0, 0, 0);
        keybd_event(VK_F9, 0, KEYEVENTF_KEYUP, 0);
        Sleep(1);
      end;
    end;
  end;
end;

// -----------------------------------------------------------------------------
{ TAcadApplicationEvents }
// -----------------------------------------------------------------------------
function TAcadApplicationEventsSink.GetTypeInfo(Index, LocaleID: Integer;
  out TypeInfo): HResult;
begin
  Result := E_NOTIMPL;
end;

function TAcadApplicationEventsSink.QueryInterface(const IID: TGUID;
  out Obj): HResult;
begin
  Result := E_NOINTERFACE;
  Pointer(Obj) := nil;
  if GetInterface(IID, Obj) then Result := S_OK;
  if IsEqualGUID(IID,_DAcadApplicationEvents) and GetInterface(IDispatch,Obj) then
    Result := S_OK;
end;

function TAcadApplicationEventsSink.GetIDsOfNames(const IID: TGUID; Names: Pointer;
  NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
  Result := E_NOTIMPL;
end;

function TAcadApplicationEventsSink._AddRef: Integer;
begin
  Result := 2;
end;
function TAcadApplicationEventsSink.GetTypeInfoCount(out Count: Integer): HRESULT;
begin
  Count := 0;
  Result := S_OK;
end;

function TAcadApplicationEventsSink.Invoke(DispID: Integer; const IID: TGUID;
  LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
  ArgErr: Pointer): HResult;
var
  FParamWideString : Widestring;
  FData : TVariantArg;
  FParamWordBool : WordBool;
begin
  Case DispID of
    2  : if Assigned(FOnNewDrawing) then FOnNewDrawing(FOwner);
    8  : if Assigned(FOnBeginQuit) then begin
           if TDispParams(Params).cArgs = 1 then begin
             FParamWordBool :=false;
             FData := PVariantArgList(TDispParams(Params).rgvarg)^[0];
             if FData.vt = VT_BOOL then FParamWordBool := FData.vbool;
             FOnBeginQuit(FOwner, FParamWordBool);
           end;
         end;
    21 : if Assigned(FOnBeginOpen) then begin
           if TDispParams(Params).cArgs = 1 then begin
             FData := PVariantArgList(TDispParams(Params).rgvarg)^[0];
             if FData.vt = VT_BSTR then FParamWideString := FData.bstrVal;
             FOnBeginOpen(FOwner, FParamWideString);
           end;
          end;
    22 : if Assigned(FOnEndOpen) then begin
           if TDispParams(Params).cArgs = 1 then begin
             FData := PVariantArgList(TDispParams(Params).rgvarg)^[0];
             if FData.vt = VT_BSTR then FParamWideString := FData.bstrVal;
             FOnEndOpen(FOwner, FParamWideString);
           end;
         end;
    else if Assigned(FOnUnknownEvent) then FOnUnknownEvent(FOwner);
  end;
  Result := S_OK;
end;

function TAcadApplicationEventsSink._Release: Integer;
begin
  Result := 1;
end;

constructor TAcadApplicationEventsSink.Create(Owner:TObject);
begin
  inherited Create;
  FOwner := Owner;
end;
// -----------------------------------------------------------------------------

// BricsCAD が起動されているか
function IsBcadActive:boolean;
const
  BcadClassName = 'BricscadApp.AcadApplication';
var
  ClassID : TGUID;
  UnKnown : IUnknown;
begin
  ClassID := ProgIDtoClassID(BcadClassName);
  result  := Succeeded(GetActiveObject(ClassID, nil, Unknown));
end;

//******************************************
// 他のプロセス内のコントロールの文字列を得る
//******************************************
function GetWindowString(h: HWND): string;
var
  p : PChar;
  len : LongInt;
begin
  result := '';
  //ウィンドウの文字列のバイト数を取得
  //終端のNULL文字を含まない文字列の長さ(バイト数)
  len := SendMessage(h, WM_GETTEXTLENGTH, 0, 0);
  if len > 0 then begin
    //終端のNULL文字を含むサイズを確保
    GetMem(p, (len + 1) * 2);
    //格納するバッファの最大サイズ(終端のNULL文字を含む長さ)
    //文字列バッファ
    SendMessage(h, WM_GETTEXT, (len + 1) * 2, LongInt(p));
    //文字列がバッファサイズより長いとき、後部がカットされる
    result := string(p);
    FreeMem(p);
  end;
end;

function GetBcadHwndCmdLineHandle(hMain: HWND): HWND;
var
  h, h2 : HWND;
  hCmdBar : HWND;
  hCmdLine : HWND;
  i : integer;
  ClassName : array [0..254] of char;
  PID, MainPID : DWORD;
begin
  FillChar(ClassName[0], 255, #0);
  GetWindowThreadProcessId(hMain, @MainPID);
  hCmdBar := FindWindowEx(hMain, 0, nil, 'コマンドライン');
  h := hCmdBar;
  for i := 1 to 4 do begin
    h2 := GetWindow(h, GW_CHILD);
    if h2 = 0 then break
    else h := h2;
  end;
  hCmdLine := GetWindow(h, GW_HWNDNEXT);
  GetClassName(hCmdLine, ClassName, 255);
  if string(ClassName) <> 'RICHEDIT50W' then hCmdLine := 0;
  if hCmdLine = 0 then begin
    hCmdBar := FindWindow(nil, 'コマンドライン');
    GetWindowThreadProcessId(hMain, @PID);
    if PID = MainPID then begin
      h := hCmdBar;
      for i := 1 to 5 do begin
        h2 := GetWindow(h, GW_CHILD);
        if h2 = 0 then break
        else h := h2;
      end;
      hCmdLine := GetWindow(h, GW_HWNDNEXT);
      GetClassName(hCmdLine, ClassName, 255);
      if string(ClassName) <> 'RICHEDIT50W' then hCmdLine := 0;
    end;
  end;
  result := hCmdLine;
end;

procedure TOneKeyForm.BcadBeginOpen(Sender: TObject; const FileName: WideString);
begin
  OneKeySleep := True;
end;

procedure TOneKeyForm.BcadEndOpen(Sender: TObject; const FileName: WideString);
begin
  OneKeySleep := True;
end;

procedure TOneKeyForm.BcadNewDrawing(Sender: TObject);
begin
  OneKeySleep := True;
end;

procedure TOneKeyForm.BcadBeginQuit(Sender: TObject; var Cancel: WordBool);
begin
end;

procedure TOneKeyForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
  ini : TIniFile;
begin
  Timer1.Enabled := False;
  try
    ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
    try
      ini.WriteInteger('OneKey', 'MouseMove', mousexy);
      ini.WriteBool('OneKey','ImeOff', CheckBox1.Checked);
      ini.WriteBool('OneKey','OneKey', CheckBox2.Checked);
    finally
      ini.Free;
    end;
  except
    ;
  end;
end;

procedure TOneKeyForm.FormCreate(Sender: TObject);
var
  ini : TIniFile;
begin
  Label1.Caption := '';
  oldCaption := '';
  // マウス移動判断の閾値の初期値
  mousexy := 20;
  mousex := 0;
  mousey := 0;

  // スクリーンの右下隅に表示
  Left := Screen.Width  - Width  - 10;
  Top  := Screen.Height - Height - 130;

  ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
  try
    mousexy := ini.ReadInteger('OneKey','MouseMove', mousexy);
    CheckBox1.Checked := ini.ReadBool('OneKey','ImeOff', True);
    CheckBox2.Checked := ini.ReadBool('OneKey','OneKey', True);
  finally
    ini.Free;
  end;

  if KeyHookHandle = 0 then
    KeyHookHandle := SetWindowsHookEx(WH_KEYBOARD_LL, @LowLevelKeyProc, hInstance, 0);
end;

procedure TOneKeyForm.FormDestroy(Sender: TObject);
begin
  try
    if KeyHookHandle <> 0 then UnhookWindowsHookEx(KeyHookHandle);
    if Assigned(EventChanel) then FreeAndNil(EventChanel);
  except
    ;
  end;
end;

procedure TOneKeyForm.FormShow(Sender: TObject);
begin
  Timer1.Enabled := True;
end;

function makeObjectSnapStr(const cmdo: string; const optstr: string): string;
begin
  result := '';
  // オプションリストに存在
  if cmdo.IndexOf(',' + optstr + ',') < 0 then begin
    // 一時オブジェクトスナップ
    // 中点:MID
    if optstr = 'M' then result := 'id' + #13
    else if optstr = 'MI' then result := 'd' + #13
    else if optstr = 'MD' then result := #08 + 'id' + #13
    else if optstr = 'MM' then result := #08 + 'id' + #13
    // 中心:CEN
    else if optstr = 'C' then result := 'en' + #13
    else if optstr = 'CE' then result := 'n' + #13
    else if optstr = 'CN' then result := #08+'en' + #13
    else if optstr = 'CC' then result := #08+'en' + #13
    // 四半円点:QUA
    else if optstr = 'Q' then result := 'ua' + #13
    else if optstr = 'QU' then result := 'a' + #13
    else if optstr = 'QA' then result := #08 + 'ua' + #13
    else if optstr = 'QQ' then result := #08 + 'ua' + #13
    // 端点:END
    else if optstr = 'E' then result := 'nd' + #13
    else if optstr = 'EN' then result := 'd' + #13
    else if optstr = 'ED' then result := #08 + 'nd' + #13
    else if optstr = 'EE' then result := #08 + 'nd' + #13
    // 交点:INT
    else if optstr = 'I' then result := 'nt' + #13
    else if optstr = 'IT' then result := #08 + 'nt' + #13
    else if optstr = 'II' then result := #08 + 'nt' + #13
    // 接線:TAN
    else if optstr = 'T' then result := 'an' + #13
    else if optstr = 'TA' then result := 'n' + #13
    else if optstr = 'TN' then result := #08 + 'an' + #13
    else if optstr = 'TT' then result := #08 + 'an' + #13
    // 近接点:NEA
    else if optstr = 'N' then result := 'ea' + #13
    else if optstr = 'NE' then result := 'a' + #13
    else if optstr = 'NA' then result := #08 + 'ea' + #13
    // 垂線:PER
    else if optstr = 'P' then result := 'er' + #13
    else if optstr = 'PE' then result := 'r' + #13
    else if optstr = 'PR' then result := #08 + 'er' + #13
    else if optstr = 'PP' then result := #08 + 'er' + #13
    // 挿入基点:INS
    else if optstr = 'S' then result := #08 + 'ins' + #13
    else if optstr = 'IS' then result := #08 + 'ns' + #13
    else if optstr = 'IN' then result := 's' + #13
    // 点:NOD
    else if optstr = 'D' then result := #08 + 'nod' + #13
    else if optstr = 'O' then result := #08 + 'nod' + #13
    else if optstr = 'ND' then result := #08 + 'od' + #13
    // 解除:NON
    else if optstr = 'NN' then result := #08 + 'on' + #13
    else if optstr = 'NO' then result := 'n' + #13
    // 2点間中点:M2P
    else if optstr = 'V' then result := #08 + 'm2p' + #13
    else if optstr = 'MT' then result := #08 + 'tp' + #13
    else if optstr = 'M2' then result := #08 + '2p' + #13
    else if optstr = 'MP' then result := #08 + '2p' + #13
    else if optstr = 'MMM' then result := #08#08 + '2p' + #13

    // 延長:EXT
    //else if optstr = 'E' then result := 'xt' + #13
    else if optstr = 'EX' then result := 't' + #13
    else if optstr = 'ET' then result := #08 + 'xt' + #13
    // 基点設定:FROM
    else if optstr = 'F' then result := 'rom' + #13
    else if optstr = 'FR' then result := 'om' + #13
    else if optstr = 'FM' then result := #08 + 'rom' + #13
    // 仮想交点:APP
    else if optstr = 'A' then result := 'pp' + #13
    else if optstr = 'AP' then result := 'p' + #13
    // 平行:PAR
    else if optstr = 'PA' then result := 'r' + #13
    //else if optstr = 'PR' then result := #08 + 'ar' + #13
    // [ESC]
    else if optstr = 'X' then result := #27
    else if optstr = 'XX' then result := #27;
  end;
end;

function makeCmdOptionList(const cmds: string): string;
var
  n, m : integer;
  s, s2 : string;
begin
  result := '';
  s := cmds;
  if (s.IndexOf('(') > 0) and (s.IndexOf(')') > 0) then begin
    m := Pos('[', s);
    n := Pos(']', s);
    if (m > 0) and (n > m) then s := Copy(s, m + 1, n-m-1);
    if s <> '' then begin
      while True do begin
        m := Pos('(', s);
        n := Pos(')', s);
        if (m > 0) and (n > m) then begin
          s2 := Copy(s, m + 1, n - m -1);
          if (s2.Length > 0) and (s2.Length <= 5) then result := result + s2 + ',';
          Delete(s, 1, n);
        end
        else break;
      end;
    end;
  end
  else if (s.IndexOf('選択:') > 0) then begin
    result := 'W,L,C,BOX,ALL,F,WP,CP,G,CL,A,R,M,P,U,AU,SI,';
  end;
  // OPT は、','で囲まれている状態にする
  if result <> '' then result := ',' + result;
end;

procedure TOneKeyForm.Timer1Timer(Sender: TObject);
var
  optstr : string; // コマンドオプション
  s : string;
  entflag, posflag : boolean;
  IME: HWND; // uses Imm
  n : integer;
  curFlag : boolean;
begin
  try
    //curFlag := False;
    // 初回起動時は1.5秒程度何もしない
    if not TmFlag then Inc(TmCnt);
    if TmCnt >= 15 then TmFlag := True;

    if TmFlag and not tmbusy then begin
      tmbusy := True;
      // [Enter]キー発行
      entflag := False;
      // コマンドライン取得
      BcadCmdHwnd := GetBcadHwndCmdLineHandle(BcadHwnd);
      if IsWindowEnabled(BcadCmdHwnd) then
        cmdstr := GetWindowString(BcadCmdHwnd)
      else
        cmdstr := '';
      if OneKeySleep then begin
        oldCaption := bcadCaption;
        TmFlag := False;
        // 0.7 秒程度何もしない
        TmCnt := 8;
        tmbusy := False;
        if Shape1.Brush.Color <> clRed then begin
          Shape1.Brush.Color := clRed;
          Shape1.Pen.Color := clRed;
        end;
        OneKeySleep := False;
      end
      else begin
        // キーフックの対象を更新
        TargetWnd := BcadHwnd;
        if (': ' <> cmdstr) then begin
          if Shape1.Brush.Color <> clWhite then begin
            Shape1.Brush.Color := clWhite;
            Shape1.Pen.Color := clWhite;
          end;
        end;
        if IsWindowEnabled(BcadHwnd) and IsWindowEnabled(BcadCmdHwnd) then begin
          posflag := True;  // 基準点を更新
          // マウス座標取得
          curFlag := GetCursorPos(pt) ;
          //curFlag := True;
          //pt := Mouse.CursorPos;
          if curFlag and (GetForegroundWindow = BcadHwnd) then begin
            s := IntToStr(pt.X - mousex) + ', ' + IntToStr(mousey - pt.Y);
            Label1.Caption := s;
          end
          else begin
            if Label1.Caption <> '' then Label1.Caption := '';
            if Shape1.Brush.Color <> clYellow then begin
              Shape1.Brush.Color := clYellow;
              Shape1.Pen.Color := clYellow;
            end;
          end;
          // コマンドオプションを取得
          cmdopt := makeCmdOptionList(cmdstr);
          // コマンド入力待ち
          if (': ' = cmdstr) then begin
            // IME Off
            if CheckBox1.Checked then begin
              if Shape1.Brush.Color <> clLime then begin
                Shape1.Brush.Color := clLime;
                Shape1.Pen.Color := clLime;
              end;
              IME := ImmGetDefaultIMEWnd(BcadCmdHwnd);
              // ON であれば OFF にする
              if Boolean(SendMessage(IME, WM_IME_CONTROL, $0005, 0)) then
                 SendMessage(IME, WM_IME_CONTROL, $0006, 0);
            end;
          end
          // コマンド入力中
          else if (cmdstr.IndexOf(':') = 0) then begin // 先頭の文字が':'
            // オプション入力待ち
            if cmdstr.Length > 2 then begin // ': '=2文字
              // 方向は右上のみ(オートコンプリート対策)
              if curFlag and ((pt.X - mousex) > mousexy) and ((mousey - pt.Y) > mousexy) then
                entflag := True;
            end;
          end
          else begin
            // オプション文字列 ':'以降
            n := LastDelimiter(':', cmdstr);
            if n > 0 then optstr := Trim(Copy(cmdstr, n + 1))
            else optstr := '';
            if optstr <> '' then begin
              optstr := UpperCase(optstr);
              // オプション文字あり
              if ((cmdstr.IndexOf('[') > 0) and (cmdstr.IndexOf(']') > 0)) or
                  (cmdstr.IndexOf('点') >= 0) or (cmdstr.IndexOf('回転') >= 0) or
                  (cmdstr.IndexOf('コーナ') >= 0) or (cmdstr.IndexOf('ポイント') >= 0) then begin
                // 方向制限なし
                if curFlag and ((abs(pt.X - mousex) > mousexy) or (abs(mousey - pt.Y) > mousexy)) then begin
                  s := Copy(optstr, 1, 1);
                  // 数値入力以外 (念のため) '-'~'9'
                  if not ((s = '@') or ((s >= '-') and (s <= '9'))) then begin
                    // 一時オブジェクトスナップ
                    s := makeObjectSnapStr(cmdopt, optstr);
                    if s <> '' then begin
                      if CheckBox2.Checked then begin
                        Timer1.Enabled := False;
                        BcadSendKeys(s);
                        Sleep(100);
                        Timer1.Enabled := True;
                      end;
                    end
                    else
                      entflag := True;
                  end;
                end;
              end;
            end;
          end;
          if curFlag and (posflag or entflag) then begin
            // 基準点を更新
            mousex := pt.X;
            mousey := pt.Y;
          end;
          if CheckBox2.Checked and entFlag then begin
            Timer1.Enabled := False;
            // [Enter] 発行
            BcadSendKeys(#13);
            Sleep(100);
            Timer1.Enabled := True;
          end;
        end;
        cmdold := cmdstr;
        tmbusy := False;

        // 追従して終了
        if not bcadflag then begin
          if IsWindow(BcadHwnd) then begin
            bcadflag := True;
            SetForegroundWindow(BcadHwnd);
          end;
        end
        else begin
          if not IsWindow(BcadHwnd) then begin
            TmFlag := False;
            TmCnt :=0;
            tmbusy := True;
            Timer1.Enabled := False;
            Close;
          end;
        end;
      end;

    end;
  except
    on E: Exception do begin
      s := E.ClassName + sLineBreak + E.Message;
      MessageBox(Handle, PChar(s), '終了します.', MB_ICONINFORMATION);
      TmFlag := False;
      TmCnt :=0;
      tmbusy := True;
      Timer1.Enabled := False;
      Close;
    end;
  end;
end;

procedure TOneKeyForm.Timer2Timer(Sender: TObject);
begin
  if not IsWindow(BcadHwnd) then begin
    if IsBcadActive  then begin
      // 起動中の Bricscad を取得
      FAcadApplication := GetActiveOleObject('BricscadApp.AcadApplication') as IACadApplication;
      if Assigned(FAcadApplication) then begin
        // ウィンドウハンドルを保持
        BcadHwnd := FAcadApplication.HWND_;
        // イベントシンク
        EventChanel := TAcadApplicationEventsSink.Create(self);
        // イベントシンクの存在をBricscad に通知
        FAcadApplication.QueryInterface(IConnectionPointContainer, CPC);
        CPC.FindConnectionPoint(_DAcadApplicationEvents, CP);
        CP.Advise(EventChanel, cookie);

        // イベントを登録
        EventChanel.OnNewDrawing    := BcadNewDrawing;
        EventChanel.OnBeginOpen     := BcadBeginOpen;
        EventChanel.OnEndOpen       := BcadEndOpen;
        EventChanel.OnBeginQuit     := BcadBeginQuit;
        Timer2.Enabled := False;
      end;
    end;
  end;
end;

end.