LauncherEX でサブモードを変更するときに入力エリアが空だったらサブモードを循環させる
以下の環境で動作確認しました。
- Windows XP Home Edition SP2
- Python 2.5.1
- CraftLaunchEx 0.991
- LauncherEX 0.0.11
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)