この文書はDr. Corran WebsterさんによるW Widgetsの"Styled Text Editor Widget"を訳したものです。
訳の公開に快く承諾してくださったDr. Corran Websterさんに感謝いたします。
内容の理解が伴っていないので、おかしいところがあるかもしれません。お気づきの点がございましたら、わたしまでお願いいたします。

[ Styled Text Window ]

Styled Text Editor Widget


新しいウィジェットクラスを作る
Bevel Button / Styled Text Editor
練習

標準のテキストウィジェットでは、ウィジェットの中でひとつのテキストスタイルしか使えません。 これはコードエディタとしてはいいのですが、WASTEには書式設定が組み込まれています。この機能が使えるテキストウィジェットがあったらいいですね。この機能を持ったW.TextEditorのサブクラスを作りましょう。

まず最初に、いくつか役に立つモジュールをインポートして、それからメニューハンドラで使う2つの定数を定義します。

import W
import Wapplication

import WASTEconst
import waste
import FrameWork

import Res
import Fm
import Menu

# 訳:スタイルとサイズメニュー。スタイルの順番は重要です(ビット値と結びついています)。

STYLES = [
    ("Bold", "B"), ("Italic", "I"), ("Underline", "U"), ("Outline", "O"),
    ("Shadow", ""), ("Condensed", ""), ("Extended", "")
    ]
SIZES = [ 9, 10, 12, 14, 18, 24]

それでは、メインとなる定義です。ベースクラスとしてW.TextEditorを使いますが、stylessoup変数を通して、テキストとともにスタイルとオブジェクトの情報を渡します。この変更に対処するために、set()get()メソッドを再定義します。また、open()close()は新たな情報を扱うということに注意してください。 使い勝手を考えて、settext()gettext()メソッドを再定義し、 スタイル情報を気にせずにテキストを手に入れられるようにします。W.TextEditorからの変更箇所を赤いハイライトで示します。

class StyledEditor(W.TextEditor):
    
    def __init__(self, possize, text = "", styles = None, soup = None, callback=None, wrap=1,
                inset=(4, 4), fontsettings=None, tabsettings=(32, 0), readonly=0):
        W.TextEditor.__init__(self, possize, text = "", callback=None, wrap=1,
                inset=(4, 4), fontsettings=None, tabsettings=(32, 0), readonly=0)
        self.tempstyles = styles
        self.tempsoup = soup
    def open(self):
        if not hasattr(self._parent, "_barx"):
            self._parent._barx = None
        if not hasattr(self._parent, "_bary"):
            self._parent._bary = None
        self._calcbounds()
        self.SetPort()
        viewrect, destrect = self._calctextbounds()
        flags = self._getflags()
        self.ted = waste.WENew(destrect, viewrect, flags)
        self.ted.WEInstallTabHooks()
        self.setfontsettings(self.fontsettings)
        self.settabsettings(self.tabsettings)
        self.ted.WEInsert(self.temptext, self.tempstyles, self.tempsoup)
        self.ted.WECalText()
        self.ted.WEResetModCount()
        if self.selection:
            self.setselection(self.selection[0], self.selection[1])
            self.selection = None
        else:
            self.selview()
        self.temptext = None
        self.tempstyles = None
        self.tempsoup = None
        self.updatescrollbars()
        self.bind("pageup", self.scrollpageup)
        self.bind("pagedown", self.scrollpagedown)
        self.bind("top", self.scrolltop)
        self.bind("bottom", self.scrollbottom)
        self.selchanged = 0
    
    def close(self):
        self.tempstyles = None
        self.tempsoup = None
        W.TextEditor.close(self)

    def set(self, text, style=None, soup=None):
        if not self.ted:
            self.temptext = text
            self.tempstyles = style
            self.tempsoup = soup
        else:
            texthandle = Res.Resource(text)
            self.ted.WESetSelection(0, self.tedWEGetTextLength())
            self.ted.WEDelete()
            self.ted.WEInsert(text, style, soup)
            self.ted.WESetSelection(0,0)
            self.ted.WECalText()
            self.SetPort()
            viewrect, destrect = self._calctextbounds()
            self.ted.WESetViewRect(viewrect)
            self.ted.WESetDestRect(destrect)
            rgn = Qd.NewRgn()
            Qd.RectRgn(rgn, viewrect)
            Qd.EraseRect(viewrect)
            self.draw(rgn)
            self.updatescrollbars()
    
    def get(self):
        if not self.ted:
            return self.temptext, self.tempstyles, self.tempsoup
        else:
            texthandle = Res.Resource('')
            styles = Res.Resource('')
            soup = Res.Resource('')
            self.ted.WECopyRange(0, self.tedWEGetTextLength(), texthandle, styles, soup)
            return text.data, styles, soup
    
    def settext(self, text):
        return W.TextEditor.set(self, text)
    
    def gettext(self):
        return W.TextEditor.get(self)

メニューアイテムその他に反応して選択範囲のテキストのスタイルを設定できるような、広範に使えるルーチンが必要です。 setstyle()がこの働きをします。 WASTEconstwhichがこのためのフラグで、これが 選択したテキストの変更したいスタイルをWASTEに伝えます。 Fontsettingsは標準のタプルで、 WASTEにフォント、スタイル、サイズ、カラーを伝えます。

    def setstyle(self, which, fontsettings):
        self.ted.WESelView()
        self.ted.WESetStyle(which, fontsettings)

また、_getflags()メソッドをオーバーライドして、WASTEに書式設定を持ったウィジェットが必要だと知らせます。

    def _getflags(self):
        flags = WASTEconst.weDoAutoScroll | WASTEconst.weDoOutlineHilite
        if self.readonly:
            flags = flags | WASTEconst.weDoReadOnly
        else:
            flags = flags | WASTEconst.weDoUndo
        return flags

最後に、もし実行可能な時にはフォント、スタイル、サイズのメニューに引っかかるよう設定する必要があります。これは、domenu_メソッドがウィジェットの中の選択されたテキストに対するメニューを適切な状態に変え、can_メソッドが選択範囲にどういうスタイルが設定されているかチェックマークを入れます。 これらが働くようにするためには、フォントメニュー(これには'.'あるいは'%'で始まらない全ての'FOND'リソース - あるいはいくつか適切なサブセット - のリストが必要です);STYLES変数にリスト化された項目(この順序は重要です)を持つスタイルメニュー;サイズメニュー(SIZESリストにあるサイズはよい選択ですが、どんな数値が含まれていてもOKです)を作ります。 メニュー項目には全て適切な'setfont''setface''setsize'のコールバックを持たせた方がいいでしょう。

    def domenu_setfont(self, id, item, window, event):
        text = Menu.GetMenuHandle(id).GetMenuItemText(item)
        font = W.GetFNum(text)
        self.setstyle(WASTEconst.weDoFont, (font, 0, 0, (0, 0, 0)))
    
    def can_setfont(self, item):
        any, mode, (font, face, size, color) = self.ted.WEContinuousStyle(WASTEconst.weDoFont)
        if any and Fm.GetFontName(font) == item.menu.items[item.item-1][0]:
            item.check(1)
        else:
            item.check(0)
        return 1
    
    def domenu_setface(self, id, item, window, event):
        face = (1 << (item-1))
        self.setstyle(WASTEconst.weDoFace | WASTEconst.weDoToggleFace,
                (0, face, 0, (0, 0, 0)))
    
    def can_setface(self, item):
        any, mode, (font, face, size, color) = self.ted.WEContinuousStyle(WASTEconst.weDoFace)
        if any and item.menu.items[item.item-1][0] in getfaces(face):
            item.check(1)
        else:
            item.check(0)
        return 1
        
    def domenu_setsize(self, id, item, window, event):
        size = Menu.GetMenuHandle(id).GetMenuItemText(item)
        self.setstyle(WASTEconst.weDoSize,
                (0, 0, int(size), (0, 0, 0)))
    
    def can_setsize(self, item):
        any, mode, (font, face, size, color) = self.ted.WEContinuousStyle(WASTEconst.weDoSize)
        if any and item.menu.items[item.item-1][0] == str(size):
            item.check(1)
        else:
            item.check(0)
        return 1

最後に、存在しない時にいろいろなメニューを設定するルーチンを定義します。 残念ながら、他の方法でメニューのコピーが得られそうなのに、手書きで作らなければなりません。 このウィジェットを使ったアプリケーションでできそうです。

def MakeFontMenu(menubar, where = 0):
        app = W.getapplication()
        fontmenu = Wapplication.Menu(menubar, "Font", where)
        for font in getfontnames():
                item = FrameWork.MenuItem(fontmenu, font, "", "setfont")
                app._menustocheck.append(item)
        return fontmenu

def MakeFaceMenu(menubar, where = 0):
        app = W.getapplication()
        facemenu = Wapplication.Menu(menubar, "Style", where)
        for face, key in STYLES:
                item = FrameWork.MenuItem(facemenu, face, key, "setface")
                app._menustocheck.append(item)
        return facemenu

def MakeSizeMenu(menubar, where=0):
        app = W.getapplication()
        sizemenu = Wapplication.Menu(menubar, "Size", where)
        for size in SIZES:
                item = FrameWork.MenuItem(sizemenu, str(size), "", "setsize")
                app._menustocheck.append(item)
        return sizemenu

def MakeNewMenus():
        mbar = W.getapplication().menubar
        MakeFontMenu(mbar)
        MakeFaceMenu(mbar)
        MakeSizeMenu(mbar)

def getfontnames():
        fontnames = []
        for i in range(1, Res.CountResources('FOND') + 1):
                r = Res.GetIndResource('FOND', i)
                name = r.GetResInfo()[2]
                if name[0] not in [".", "%"]:
                        fontnames.append(name)
        fontnames.sort()
        return fontnames
        
def getfaces(face):
        faces = []
        for i in range(len(STYLES)):
                if face & (1 << i):
                        faces.append(STYLES[i][0])
        return faces

以上を一つにまとめたまとめたモジュールをstylededitor.pyで見ることができます。どのように動くか、IDEで"Run as __main__"を設定して実行してみてください。 IDEでフォント、スタイル、サイズのメニューを得るには、stylededitor.pyをモジュール化して(IDEで"modularize"を設定して)、コンソールウィンドウで以下のようにタイプします:

>>> import stylededitor
>>> stylededitor.MakeNewMenus()

理想的には、書式のメニューに他の便利な項目、サイズメニューに"大きく"、"小さく"、"その他"、そしてスタイルメニューには"Plain"のオプションがあればいいでしょう。 また、カラーメニューやテキストを揃えるなどの設定を加えることができるでしょう。 もう少し工夫すれば、このウィジェットをSimpleTextと同等のパワーを持つシンプルなエディタや、 シンプルなHATMLビューアに作り上げることも可能でしょう。

練習

  1. サイズメニューに"大きく"と"小さく"のメニューアイテムを作り、そのためのdomenu_とコマンドを加えましょう。
  2. スタイルメニューに"Plain"メニューアイテムを作り、domenu_setfacecan_setfaceを設定しましょう。
  3. サイズメニューに"その他"のメニューアイテムを作り、機能させましょう。 これはダイアログを出し、ユーザに希望するサイズを入力させるようにします。
  4. 定義では、スタイルメニューはあまり軽快ではありません:STYLESのリストは順番に現れます(もし練習2を済ませたら、気が付いたでしょう)。もっとちゃんと動くように作り直しましょう。