LauncherEX でサブモードを変更するときに入力エリアが空だったらサブモードを循環させる

以下の環境で動作確認しました。

LauncherEX は、デフォルトで Ctrl-. に Shortcut_ChangeSubMode() がバインドされています。
この関数は、コマンド入力エリアの文字列を OPTION_EX_SUBMODE にセットします。
これを、コマンド入力エリアが空のときは、サブモードのリストで現在のモードの次の位置にあるモードを、OPTION_EX_SUBMODE にセットするようにします。
現在のサブモードがリストの末尾だったり、リストにない場合は、リストの先頭にあるモードをセットします。現在のサブモードがリストにない場合は、現在のモードをリストの末尾に追加してからモードを変更します。
クラス LauncherModeEX のメソッド Shortcut_ChangeSubMode を置き換えます。
'_OPTION_EX_SUBMODE_LIST' というオプションを追加します。数値にするとカブる可能性があるので、文字列にしてあります。
このオプションには、サブモードのリストをセットします。リストには、サブモードのファイル名から拡張子を除いた名前をセットします。
デフォルトでは『CraftLaunchEx のあるディレクトリパス\extension\clmode_lex\clsubmode_*.py』のパターンにマッチするファイルがセットされています。

コード

config.py などに記述します。

## LauncherEX
import clmode_lex # `from clconst import *' より前に記述する


from clapi import *
from clconst import *


import cloption

cloption._set_option_func_table['_OPTION_EX_SUBMODE_LIST'] \
    = cloption._SetOption_Simple
cloption._get_option_func_table['_OPTION_EX_SUBMODE_LIST'] \
    = cloption._GetOption_Simple

import glob
import os.path

SetOption('_OPTION_EX_SUBMODE_LIST', [
    os.path.splitext(os.path.split(x)[1])[0] for x
    in glob.glob('%s*.py'
                 % os.path.join(GetAppDir(),
                                r'extension\clmode_lex\clsubmode_'))
])

def _LauncherModeEXShortcutChangeSubMode(self):
    u"""サブモードを変更します"""
    import clcore
    from clmode_lex import OPTION_EX_SUBMODE

    s = GetValue()
    if s:
        SetOption(OPTION_EX_SUBMODE, s)
        SetValue('')
    else:
        lst = GetOption('_OPTION_EX_SUBMODE_LIST')
        submode = GetOption(OPTION_EX_SUBMODE)
        try:
            idx = (lst.index(submode) + 1) % len(lst)
        except ValueError:
            lst.append(submode)
            idx = 0
        SetOption(OPTION_EX_SUBMODE, lst[idx])
    clcore.RaiseNextWindow()
clmode_lex.clLauncherEX.LauncherModeEX.Shortcut_ChangeSubMode \
    = _LauncherModeEXShortcutChangeSubMode

以下のコードは、この関数をテストするためにシャレで作ったサブモード『保冷所』です。
アメリカの TV シリーズ『CSI:マイアミ』の登場人物であるホレイショ・ケインの顔文字『(`●ω●´)』に、台詞を言わせます。
台詞は、ホレイショ名ゼリフTOP10 から引用しました。

# -*- coding: utf-8 -*-

u"""clModeLEXSubModeHoracio - 保冷所

This script is sub mode for LauncherEX.
Horacio keeps on speaking his lines while CraftLaunchEx is unfocused.
"""

version = '0.0.0.0'


import sys

(major, platform) = sys.getwindowsversion()[0:4:3]
winNT5OrLater = (platform == 2) and (major >= 5)

del sys, major, platform # Not for export


import ctypes

class c_tchar(ctypes._SimpleCData):
    if winNT5OrLater:
        _type_ = 'u' # c_wchar
    else:
        _type_ = 'c' # c_char


from ctypes.wintypes import BYTE, LONG

# WinUser.h
WM_GETFONT = 0x0031

if winNT5OrLater:
    SendMessage = ctypes.windll.user32.SendMessageW
else:
    SendMessage = ctypes.windll.user32.SendMessageA
GetDlgItem = ctypes.windll.user32.GetDlgItem
GetSystemMetrics = ctypes.windll.user32.GetSystemMetrics
GetDC = ctypes.windll.user32.GetDC


# WinGDI.h
LF_FACESIZE = 32
LOGPIXELSY = 90

class LOGFONT(ctypes.Structure):
    _fields_ = [
        ('lfHeight',         LONG),
        ('lfWidth',          LONG),
        ('lfEscapement',     LONG),
        ('lfOrientation',    LONG),
        ('lfWeight',         LONG),
        ('lfItalic',         BYTE),
        ('lfUnderline',      BYTE),
        ('lfStrikeOut',      BYTE),
        ('lfCharSet',        BYTE),
        ('lfOutPrecision',   BYTE),
        ('lfClipPrecision',  BYTE),
        ('lfQuality',        BYTE),
        ('lfPitchAndFamily', BYTE),
        ('lfFaceName',       c_tchar*LF_FACESIZE)
    ]

GetDeviceCaps = ctypes.windll.gdi32.GetDeviceCaps
if winNT5OrLater:
    GetObject = ctypes.windll.gdi32.GetObjectW
else:
    GetObject = ctypes.windll.gdi32.GetObjectA


from clapi import *
from clconst import *


format = u'(`●ω●´)<%s'
oneLiners = [
    u'俺は決してお前を許さない。必ずお前を塀の中へブチ込んでやる。分かったか?',
    u'我々 CSI は決して…そう、決してあきらめない。',
    u'スピードル、お前のおかげだ。',
    u'もう行け、いいな? 元気でな…さあ。',
    u'それが俺の仕事だ。俺は悪を始末する。',
    u'お前の人生を滅茶苦茶にしてやってもいいんだぞ。',
    u'復讐は正義ではない、犯罪で正義はなし得ない。',
    u'必要ない。この現場が語ってくれる。',
    u'お前みたいな蛆虫を一生ムショにブチ込んでやること、それが俺の究極のスリルだ!',
    u'私を…敵にしない方がいい。'
]
interval = 200
atRandom = False
repeatCount = 1
charNum = 11
fontName = u'MS Pゴシック'
minWidth = 0
position = None # (x, y)
fgColor = None # (R, G, B)
bgColor = None # (R, G, B)


import clmode
import clcore
import random

class SubModeHoracio(clmode.BaseMode):

    def __init__(self, prevMode):
        clmode.BaseMode.__init__(self)
        self.__PrevMode = prevMode
        # identifier of the edit control: 1000
        self.__PrevFont = _GetFont(GetDlgItem(GetHandle(), 1000))
        self.__PrevMinWidth = GetOption(OPTION_EDIT_MIN_WIDTH)
        self.__PrevFGColor = GetOption(OPTION_FG_COLOR)
        self.__Format = format
        self.__OneLiners = oneLiners
        self.__OneLinersIndex = -1
        self.__Interval = interval
        self.__Random = atRandom
        self.__RepeatCount = repeatCount if repeatCount > 0 else 1
        self.__Counter = self.__RepeatCount
        self.__CharNum = charNum if charNum > 0 else 1
        self.__CharIndex = -self.__CharNum
        self.__MaxCharIndex = -self.__CharNum
        self.__FontName = fontName
        self.__MinWidth = minWidth
        self.__Position = position
        self.__FGColor = fgColor
        self.__BGColor = bgColor

    def OnGetControl(self):
        if self.__FontName:
            SetFont(self.__FontName, *self.__PrevFont[1:])
        self.__SetMinWidth()
        self.__SetPosition()
        if self.__FGColor:
            SetFgColor(*self.__FGColor)
        if self.__BGColor:
            SetBgColor(*self.__BGColor)
        SetStatusIndicator(u'保')
        AddTimerHandler(self.__Interval, self.OnTimer)
        self.OnTimer()

    def OnLoseControl(self):
        RemoveTimerHandler(self.OnTimer)
        SetValue('')
        SetFont(*self.__PrevFont)
        SetOption(OPTION_EDIT_MIN_WIDTH, self.__PrevMinWidth)

    def OnActivate(self, event):
        clmode.BaseMode.OnActivate(self, event)
        if event.active:
            SetPos(*GetOption(OPTION_ORIGINAL_POSITION))
            SetFgColor(*self.__PrevFGColor)
            PopMode()
            clmode.Top().OnActivate(event)

    def OnTimer(self):
        if not IsActive():
            self.__Speak()

    def __SetMinWidth(self):
        if self.__MinWidth:
            n = self.__MinWidth
        else:
            hwnd = GetHandle()
            hEdit = GetDlgItem(hwnd, 1000)
            lFont = _GetLogFont(hEdit)
            if lFont:
                (left, right) = GetWindowRect(hEdit)[0:3:2]
                n = (max([clcore.Edit_GetTextWidth(x) for x
                          in [self.__Format % x[:self.__CharNum] for x
                              in self.__OneLiners]])
                     + int(-lFont.lfHeight * 1.5)
                     + left + GetWindowRect(hwnd)[2] - right
                     + GetSystemMetrics(SM_CXEDGE) * 2)
            else:
                n = 0
        SetOption(OPTION_EDIT_MIN_WIDTH, n)

    def __SetPosition(self):
        if self.__Position:
            (x, y) = self.__Position
        else:
            (x, y) = clcore.GetSystemParametersInfo(SPI_GETWORKAREA)[0:4:3]
            (top, bottom) = GetWindowRect(GetHandle())[1:4:2]
            y += top - bottom
        SetPos(x, y)

    def __SetNextOneLinersIndex(self):
        if self.__Counter < self.__RepeatCount:
            self.__Counter += 1
        else:
            if self.__Random:
                try:
                    self.__OneLinersIndex = random.choice([
                        i for i in xrange(0, len(self.__OneLiners))
                        if i != self.__OneLinersIndex
                    ])
                except IndexError:
                    self.__OneLinersIndex = 0
            else:
                self.__OneLinersIndex = \
                    (self.__OneLinersIndex + 1) % len(self.__OneLiners)
            self.__Counter = 1

    def __Speak(self):
        if self.__CharIndex >= self.__MaxCharIndex:
            self.__SetNextOneLinersIndex()
            self.__CharIndex = -self.__CharNum
            self.__MaxCharIndex = len(self.__OneLiners[self.__OneLinersIndex])
        self.__CharIndex += 1
        if self.__CharIndex < 0:
            start = 0
        else:
            start = self.__CharIndex
        end = self.__CharIndex + self.__CharNum
        if self.__OneLiners[self.__OneLinersIndex][start:end].endswith(' '):
            end += 1
            self.__CharIndex += 1
        SetValue(self.__Format
                 % self.__OneLiners[self.__OneLinersIndex][start:end])


def OnSubmode():
    PushMode(SubModeHoracio(clmode.Top()))


from ctypes.wintypes import HGDIOBJ, SIZE

def _GetLogFont(hwnd):
    hFont = SendMessage(hwnd, WM_GETFONT, 0, 0)
    if not hFont:
        return
    lFont = LOGFONT()
    if GetObject(hFont, ctypes.sizeof(LOGFONT), ctypes.byref(lFont)):
        return lFont

def _GetFont(hwnd):
    lFont = _GetLogFont(hwnd)
    if lFont:
        faceName = lFont.lfFaceName
        if isinstance(faceName, str):
            faceName = unicode(string, 'mbcs')
        size = int(float(-lFont.lfHeight)
                   / GetDeviceCaps(GetDC(hwnd), LOGPIXELSY)
                   * 72)
        return (faceName, size, lFont.lfWeight, lFont.lfItalic)

xml-mode の CraftLaunchEx 用キーワード定義

以下の環境で動作確認しました。

xml-modeCraftLaunchEx のコマンド定義ファイル command.xml の要素/属性名に色をつけたり、補完できるようにします。
キーワード定義は CraftLaunchEx 0.991 のものです。

コード

(in-package "xml")

(defvar *xml-mode-craftlaunchex-suffix* nil)

(defvar *xml-mode-craftlaunchex-keyword-list*
  '((t
     "craftlaunch"
     "command-group"
     "shellexec"
     "activate"
     "activate-or-shellexec"
     "url"
     "script"
     "switch"
     "@name"
     "@verb"
     "@file"
     "@param"
     "@directory"
     "@swmode"
     "@window-class"
     "@window-class-re"
     "@window-title"
     "@window-title-re"
     "@url"
     "@encoding"
     "@paramlist"
     "@format"
     "@mod"
     )))

(defvar *xml-mode-craftlaunchex-guess-list*
  (let ((cmdelmlst '("shellexec" "activate" "activate-or-shellexec" "url"
                     "script" "switch"
                     )))
    (list (cons ""
                '("craftlaunch"))
          (cons "command-group"
                cmdelmlst)
          (cons "switch"
                (remove "switch" cmdelmlst :test #'string=))
          )))

(pushnew (list "CraftLaunchEx"
               nil
               nil
               nil
               nil
               nil
               *xml-mode-craftlaunchex-keyword-list*
               *xml-mode-craftlaunchex-guess-list*)
         *xml-doctypes*
         :test #'equal)

(in-package "user")

設定例

*xml-mode-craftlaunchex-suffix* に command.xml のパスの正規表現をセットして、*xml-auto-doctype-alist* と *auto-mode-alist* に push します。

(setq xml::*xml-mode-craftlaunchex-suffix*
      (concat "^"
              (regexp-quote (merge-pathnames "../CLNCHEX/command.xml"
                                             (si:system-root)))
              "$"))
(pushnew (list xml::*xml-mode-craftlaunchex-suffix* nil "CraftLaunchEx")
         *xml-auto-doctype-alist*
         :test #'equal)
(pushnew (cons xml::*xml-mode-craftlaunchex-suffix* 'xml-mode)
         *auto-mode-alist*
         :test 'equal)

コマンドラインオプションを解析する

以下の環境で動作確認しました。

UWSC で、コマンドラインオプションを解析します。
オプションは、ハイフンで始まって 1 つの文字が続くものにしか対応していません。

コード

OPTION EXPLICIT


// コマンドラインオプションを解析する
//
// 引数  : argv - 配列 [入力] 解析対象の配列
// 引数  : optstr - 文字列 [入力] 認識させたいオプション文字の集合
//         引数が必要な場合には文字の後ろに `:' を付ける
// 引数  : opthash - 連想配列 [入力/出力] キーにオプション、値に引数をセットした
//         連想配列がセットされる
//         オプションに引数がない場合は値に空文字がセットされる
// 引数  : argary - 配列 [入力/出力] 解析対象のオプション以外の要素がセットされる
// 引数  : opterr - 真偽値 [入力] TRUE を指定すると、エラーが発生した場合にメッ
//         セージボックスを出し、終了コード 1 で直ちにスクリプトを終了する
//         エラーになるのは以下の場合
//           * 認識できないオプションがあった
//           * 引数が必要なオプションに引数が与えられなかった
//         FALSE を指定するとメッセージボックスを出さず、また、スクリプトも終了
//         せずに処理を続ける
//         省略時は TRUE を指定したものとして動作する
//
// `-' と `--' はオプションとみなされない。
// `--' 以降は解析対象から外され、`--' より後は全て argary にセットされる。
// 同じオプションは後のもので上書きされる。
// エラーが発生したオプションは、opthash と argary のどちらにもセットされない。
// 大文字小文字を区別したいときは `OPTION SAMESTR' を指定し、連想配列を宣言する
// ときに `HASH_CASECARE' を指定する。
PROCEDURE getopt(argv[], optstr, VAR opthash[], VAR argary[], opterr=TRUE)
  DIM argc, n, i, j, isstop

  RESIZE(argary, -1)
  argc = LENGTH(argv)
  n = LENGTH(optstr)
  i = 0 // argv
  j = 0 // argary
  isstop = FALSE
  WHILE i < argc
    IFB isstop THEN // 解析中止
      RESIZE(argary, j)
      argary[j] = argv[i]
      j = j + 1
    ELSEIF (argv[i] = "--") THEN
      isstop = TRUE
    ELSE
      DIM k = 1 // optstr

      WHILE k <= n
        DIM c, argflg

        c = COPY(optstr, k, 1)
        argflg = FALSE
        IFB COPY(optstr, k+1, 1) = ":" THEN
          argflg = TRUE
          k = k + 1
        ENDIF

        IFB argv[i] = ("-" + c) THEN
          IFB argflg THEN // 引数が必要なオプション
            IFB argc - i = 1 THEN
              IFB opterr THEN
                MSGBOX(argv[i] + ": 引数が必要")
                EXITEXIT 1
              ENDIF
              BREAK
            ELSEIF (argv[i+1] <> "-") AND (POS("-", argv[i+1]) = 1) THEN
              IFB opterr THEN
                MSGBOX(argv[i] + ": 引数が必要")
                EXITEXIT 1
              ENDIF
              BREAK
            ENDIF
            opthash[argv[i]] = argv[i+1]
            i = i + 1
            BREAK
          ENDIF
          opthash[argv[i]] = ""
          BREAK
        ELSEIF k = n THEN // optstr が最後までイった
          IFB (argv[i] <> "-") AND (POS("-", argv[i]) = 1) _
              AND (LENGTH(argv[i]) = 2) AND !CHKNUM(argv[i]) THEN
            IFB opterr THEN
              MSGBOX(argv[i] + ": 無効なオプション")
              EXITEXIT 1
            ENDIF
            BREAK
          ELSE
            RESIZE(argary, j)
            argary[j] = argv[i]
            j = j + 1
          ENDIF
        ENDIF

        k = k + 1
      WEND
    ENDIF

    i = i + 1
  WEND
FEND

使用例

上記のコードを『_getopt.uws』というファイル名で保存したとします。

// test.uws: テスト

OPTION EXPLICIT
OPTION LOGFILE = 2

CALL _getopt.uws

HASHTBL opthash
DIM argary[0], i

getopt(PARAM_STR, "a:b", opthash, argary)

IF opthash["-a", HASH_EXISTS] THEN PRINT "-a: "+opthash["-a"]
IF opthash["-b", HASH_EXISTS] THEN PRINT "-b: "+opthash["-b"]

FOR i=0 TO LENGTH(argary)-1
  PRINT argary[i]
NEXT

という内容のファイル『test.uws』を作成して、

UWSC test.uws -a aaa -b ccc d e f

というコマンドラインを実行すると、ログファイルに以下の文字列が印字されます。

-a: aaa
-b: 
ccc
d
e
f

HTML ヘルプを表示する

以下の環境で動作確認しました。

CraftLaunchEx で、HTML Help API を使用して HTML ヘルプを表示します。

コード

config.py などに記述します。
Windows 2000/XP では Unicode 版 (HtmlHelpW) を使用し、2000/XP 以外では ANSI 版 (HtmlHelpA) を使用します。

def _IsWinNT5OrLater():
    import sys

    (major, platform) = sys.getwindowsversion()[0:4:3]
    # VER_PLATFORM_WIN32_NT: 2
    return ((platform == 2) and (major >= 5))

winNT5OrLater = _IsWinNT5OrLater()


def _T(string):
    if isinstance(string, str):
        string = unicode(string, 'utf-8')
    if not winNT5OrLater:
        return string.encode('mbcs')
    return string


import ctypes

class c_tchar_p(ctypes._SimpleCData):
    if winNT5OrLater:
        _type_ = 'Z' # c_wchar_p
    else:
        _type_ = 'z' # c_char_p


# WinUser.h
GetDesktopWindow = ctypes.windll.user32.GetDesktopWindow


# HtmlHelp.h
HH_DISPLAY_TOC    = 0x0001
HH_KEYWORD_LOOKUP = 0x000D
HH_CLOSE_ALL      = 0x0012


from ctypes.wintypes import BOOL

class HH_AKLINK(ctypes.Structure):
    _fields_ = [
        ('cbStruct',     ctypes.c_int), # sizeof this structure
        ('fReserved',    BOOL),         # must be FALSE (really!)
        ('pszKeywords',  c_tchar_p),    # semi-colon separated keywords
        ('pszUrl',       c_tchar_p),    # URL to jump to if no keywords found (may be NULL)
        ('pszMsgText',   c_tchar_p),    # Message text to display in MessageBox if pszUrl is NULL and no keyword match
        ('pszMsgTitle',  c_tchar_p),    # Message text to display in MessageBox if pszUrl is NULL and no keyword match
        ('pszWindow',    c_tchar_p),    # Window to display URL in
        ('fIndexOnFail', BOOL)          # Displays index if keyword lookup fails.
    ]


if winNT5OrLater:
    HtmlHelp = ctypes.windll.LoadLibrary('hhctrl.ocx').HtmlHelpW
else:
    HtmlHelp = ctypes.windll.LoadLibrary('hhctrl.ocx').HtmlHelpA


def HtmlHelpDisplayTOC(chmPath, data=None):
    u"""指定されたヘルプウィンドウでヘルプトピックを開く

引数  : chmPath - 文字列 - コンパイル済みヘルプまたはコンパイル済みヘルプファイ
        ル中のトピック
引数  : data - 数値 - コンパイル済みヘルプファイル中のトピックへのポインタ
戻り値: ヘルプウィンドウのハンドルを返す"""
    return HtmlHelp(GetDesktopWindow(), chmPath, HH_DISPLAY_TOC, data)

def HtmlHelpKeywordLookup(chmPath, kwd):
    u"""コンパイル済みヘルプファイルからキーワードを検索する

引数  : chmPath - 文字列 - コンパイル済みヘルプファイル
引数  : kwd - 文字列 -  検索するキーワード
        複数の項目はセミコロン `;' で区切る
戻り値: ヘルプウィンドウのハンドルを返す"""
    aklnk = HH_AKLINK(ctypes.sizeof(HH_AKLINK), False, kwd, None, None, None,
                      None, True)
    return HtmlHelp(GetDesktopWindow(), chmPath, HH_KEYWORD_LOOKUP,
                    ctypes.byref(aklnk))

def HtmlHelpCloseAll():
    u"""呼び出しプログラムによって開かれたヘルプウィンドウをすべて閉じる

戻り値: なし"""
    HtmlHelp(None, None, HH_CLOSE_ALL, 0)

コマンド定義の例

CraftLaunchEx Help』を表示します。引数があればキーワード検索します。
ControlShift を押しながら実行すると、ヘルプウィンドウを閉じます。

<switch name='Help'>
  <script mod='' paramlist='*args' format=''>
    <![CDATA[
import os.path

hwnd = FindWindow('HH Parent', 'CraftLaunchEx Help')
chm = _T(os.path.join(GetAppDir(), 'clnch.chm'))

if hwnd:
    SetForegroundWindow(hwnd)
else:
    HtmlHelpDisplayTOC(chm + _T('::/chapter-01.html#%82%CD%82%B6%82%DF%82%C9'))
if len(args):
    HtmlHelpKeywordLookup(chm, _T(args[0]))
      ]]>
  </script>
  <script mod='CS' paramlist='' format=''>
    <![CDATA[
HtmlHelpCloseAll()
      ]]>
  </script>
</switch>

最初の script 要素の paramlist 属性値が『*args』となっているのは、

config.py に以下を書いてみたら、paramlist に *args とか書いて
余分な引数をタプルとして受け取れるようにできたよ

def MyCmdScriptCall(self, mod, *args):
    if len(args) < len(self.format):
        raise CommandFailedException, 'parameter num mismatch.'

    param_objects = []
    for (f, a) in zip(self.format, args):
        if  f == 's': param_objects.append(a)
        elif f == 'i': param_objects.append(int(a))
        elif f == 'f': param_objects.append(float(a))
    param_objects.extend( args[len(self.format):] )

    self.func( *param_objects )

CmdScript.__call__ = MyCmdScriptCall
http://pc11.2ch.net/test/read.cgi/software/1118294183/533

で、引数をタプルで取得できるようにしているからです。
_T() は VC++ の _T マクロみたいなものです。Windows 2000/XP なら unicode に、2000/XP 以外なら mbcs にエンコードします。

CraftLaunchEx のウィンドウを移動する

以下の環境で動作確認しました。

CraftLaunchEx のウィンドウを移動させます。
フォーカスを失えば、初期位置に戻ります。
キーバインドと移動する方向は以下。

キー 方向
Alt-Shift-Up
Alt-Shift-Right
Alt-Shift-Down
Alt-Shift-Left

ハンドラの仮引数 n のデフォルト値をお好みの数値に変更して下さい。
デフォルトでは 10px ずつ移動します。

コード

config.py などに記述します。

from clapi import *
from clconst import *


def MoveCLnchExWindow(x, y):
    u"""CraftLaunchEx のウィンドウを移動する

引数  : x - 整数 - X 座標に加算するピクセル数
引数  : y - 整数 - Y 座標に加算するピクセル数
戻り値: なし"""
    (left, top) = GetWindowRect(GetHandle())[0:2]
    SetPos(left+x, top+y)


def _MoveUpCLnchExWindow(n=10):
    u"""CraftLaunchEx のウィンドウを上へ移動する"""
    MoveCLnchExWindow(0, -n)
SetKeyDownHandler(VK_UP, MODKEY_ALT | MODKEY_SHIFT, _MoveUpCLnchExWindow)

def _MoveRightCLnchExWindow(n=10):
    u"""CraftLaunchEx のウィンドウを右へ移動する"""
    MoveCLnchExWindow(n, 0)
SetKeyDownHandler(VK_RIGHT, MODKEY_ALT | MODKEY_SHIFT, _MoveRightCLnchExWindow)

def _MoveDownCLnchExWindow(n=10):
    u"""CraftLaunchEx のウィンドウを下へ移動する"""
    MoveCLnchExWindow(0, n)
SetKeyDownHandler(VK_DOWN, MODKEY_ALT | MODKEY_SHIFT, _MoveDownCLnchExWindow)

def _MoveLeftCLnchExWindow(n=10):
    u"""CraftLaunchEx のウィンドウを左へ移動する"""
    MoveCLnchExWindow(-n, 0)
SetKeyDownHandler(VK_LEFT, MODKEY_ALT | MODKEY_SHIFT, _MoveLeftCLnchExWindow)

LauncherEX で最後に実行したコマンドを実行する

以下の環境で動作確認しました。

LauncherEX をモード内のみで有効なキーバインドを設定できるようにする - anonymous苦労者 のコンストラクタ置き換えをしているものとします。
LauncherEX 0.0.11 以降は、コンストラクタの書き換えは不要になりました。
LauncherEXで、コマンド実行時に _SetLastCommand() で、インスタンス変数 _LastCommand に (コマンド名, モディファイアキー) のタプルを記録し、_RepeatLastCommand() で変数 _LastCommand を Execute() します。
_RepeatLastCommand() で AttributeError を握り潰しているのは、関数実行時に変数が存在しない場合があるからです。

コード

config.py などに記述します。

## LauncherEX
import clmode_lex # `from clconst import *' より前に記述する


from clapi import *
from clconst import *


# コマンドを記録する
def _SetLastCommand(event):
    import clmode
    clmode.Top()._LastCommand = (event.str, event.mod)

def _RepeatLastCommand():
    u"""最後に実行したコマンドを実行する"""
    import clmode
    mode = clmode.Top()
    assert isinstance(mode, clmode_lex.clLauncherEX.LauncherModeEX)
    try:
        Execute(*mode._LastCommand)
    except AttributeError:
        pass

# 初期化時
def _LEXHookOnInit(event):
    event.keys.update({
        (ord('I'), MODKEY_ALT) : _RepeatLastCommand
    })
clmode_lex.AddHook(clmode_lex.lex_hook_on_init, _LEXHookOnInit)
PopMode()
PushMode(clmode_lex.clLauncherEX.LauncherModeEX())

# コマンド実行前
clmode_lex.AddHook(clmode_lex.lex_hook_on_before_command, _SetLastCommand)

# コマンド実行後
clmode_lex.AddHook(clmode_lex.lex_hook_on_after_command, _SetLastCommand)

LauncherEX をモード内のみで有効なキーバインドを設定できるようにする

LauncherEX 0.0.11 以降は、コンストラクタの書き換えは不要になりました。
以下の環境で動作確認しました。

LauncherEXCraftLaunchEx の LauncherMode を拡張するスクリプトです。
これをモード内のみで有効なキーバインドを設定できるようにします。
LauncherEX のコンストラクタを置き換えます。
lex_hook_on_init のフック関数で、引数 event の属性 keys にアクセスして、キーバインドとハンドラを設定します。
keys は『{ (仮想キーコード, モディファイアキー) : ハンドラ }』という辞書です。

コード

config.py などに記述します。

## LauncherEX
import clmode_lex # `from clconst import *' より前に記述する


from clapi import *
from clconst import *


# コンストラクタ
def _LauncherModeEXInit(self):
    import clmode_launcher
    clmode_launcher.LauncherMode.__init__(self)
    self.cmd = ''
    self.sel = None

    import clevent
    event = clevent.Event()
    # 0xBE: VK_OEM_PERIOD
    # { (仮想キーコード, モディファイアキー) : ハンドラ }
    event.keys = { (0xBE, MODKEY_CTRL) : self.Shortcut_ChangeSubMode }

    err = clmode_lex.RunHook(clmode_lex.lex_hook_on_init, event)
    if err: raise err

    for (k, v) in event.keys.iteritems():
        self.SetKeyDownHandler(k[0], k[1], v)
clmode_lex.clLauncherEX.LauncherModeEX.__init__ = _LauncherModeEXInit


def _Hoge():
    print 'hoge'

# 初期化時
def _LEXHookOnInit(event):
    event.keys.update({
        # Alt-h に _Hoge() をバインド
        (ord('H'), MODKEY_ALT) : _Hoge
    })
clmode_lex.AddHook(clmode_lex.lex_hook_on_init, _LEXHookOnInit)

# lex_hook_on_init を発火させるには PopMode() して PushMode()
PopMode()
PushMode(clmode_lex.clLauncherEX.LauncherModeEX())