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

[ Bevel Buttons ]

Bevel Button Widget


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

MacOS8の導入により、標準のコントロールがたくさん付け加えられました。そのうちのひとつが ベベルボタンです。これはふつうのボタンのように働きますが、ふつうの角の丸い矩形ではなく、3Dの外観を持った矩形です。これにははっきりとした外観の違いから3つの種類があります。W.Buttonのサブクラスを作って、これら新しいコントロールを標準のプッシュボタンの代わりに表示してみましょう。

はじめに、W.Buttonクラスを呼び出すためにWモジュールを、Control Managerツールボックス呼び出すためにCtlモジュールを、Control Managerの定数を呼び出すためにControlsモジュールをインポートします。また、デフォルトであることを示すリングを描くのにQdモジュールが、Wbaseモジュールにあるベベルアピアランスを補助するルーチンが必要です。

import W
import Ctl
import Controls
import Qd

from Wbase import _darkencolor

はじめに定義するのは標準のベベルボタンで、Buttonクラスのサブクラスとします:

class NormalBevelButton(W.Button):

ベベルボタンは種類ごとにそれぞれ固有のコントロール定義で作られます。 他の種類のベベルボタンを作るためにも、この操作を簡単にするためにクラス変数として コントロールID(procID)を定義します。

    procID = Controls.kControlBevelButtonNormalBevelProc

たいていの Button メソッドは上で作った新しいボタンクラスで働きますが、Button.__init__によって、コントロールID(procID)はpushButProcであると決められてしまいます。 それで、これをオーバーライドして正しいコントロールIDを使えるようにします:

    def __init__(self, possize, title = "BevelButton", callback = None):
        W.ControlWidget.__init__(self, possize, title, self.procID, callback, 0, 0, 1)
        self._isdefault = 0

ボタンがウィンドウのデフォルトボタンなら何が起こるかを変える必要もあります。ふつうはこれをアピアランスマネージャが自動的にしてくれますが、Ctlモジュールではまだこれが実装されていません。かわりにデフォルトボタンの周りのリングを手書きで描かなければなりません。

これを扱うのはdrawfatframe()メソッドです。 変更は全く簡単です(複雑に見えますが、それは描画コマンドのせいです):

    def drawfatframe(self, onoff):
        # draw a platinum appearance default box
        color = (0xe000, 0xe000, 0xe000)
        (l, t, r, b) = Qd.InsetRect(self._bounds, -4, -4)
        if onoff:
            # Draw the frame
            Qd.FrameRect((l, t, r, b))
            Qd.RGBForeColor(color)
            Qd.PaintRect((l+1, t+1, r-1, b-1))
            Qd.RGBForeColor(_darkencolor(color))
            Qd.MoveTo(l+2, b-2)
            Qd.LineTo(r-2, b-2)
            Qd.LineTo(r-2, t+2)
            Qd.RGBForeColor(_darkencolor(color))
            (l, t, r, b) = Qd.InsetRect(self._bounds, -1, -1)
            Qd.PaintRect((l, t, r, b))
            Qd.RGBForeColor((0, 0, 0))
            Qd.RGBForeColor(_darkencolor(color))
            Qd.MoveTo(l, b-1)
            Qd.LineTo(r-1, b-1)
            Qd.LineTo(r-1, t)
        else:
            # Erase the frame
            Qd.RGBForeColor((0xffff, 0xffff, 0xffff))
            Qd.PaintRect((l, t, r, b))
        Qd.RGBForeColor((0, 0, 0))

しかし、不精をしてQd.FrameRectではなく、Qd.PaintRectを使って矩形を描きました。こうするとボタンの上に矩形を描きます。これを解決するには、いくつかルーチンを書き直して、デフォルトボタンであることを示すリングを描いた後にボタンを描くようにしなければなりません。

    def enable(self, onoff):
        if self._control and self._enabled != onoff:
            self._enabled = onoff
            if self._isdefault and self._visible:
                self.SetPort()
                self.drawfatframe(onoff)
            self._control.HiliteControl((not onoff) and 255)
    
    def activate(self, onoff):
        self._activated = onoff
        if self._enabled:
            if self._isdefault and self._visible:
                self.SetPort()
                self.drawfatframe(onoff)
            self._control.HiliteControl((not onoff) and 255)

    def draw(self, visRgn = None):
        if self._visible:
            if self._isdefault and self._activated:
                self.drawfatframe(self._enabled)
            self._control.Draw1Control()
    
    def _setdefault(self, onoff):
        self._isdefault = onoff
        if self._control and self._enabled:
            self.SetPort()
            self.drawfatframe(onoff)
            self.draw()

さあ、これで面倒なところは全部終わりました。残り2つの種類のベベルボタンのクラス定義は 簡単です:

class SmallBevelButton(NormalBevelButton):
	ProcID = Controls.kControlBevelButtonSmallBevelProc

class LargeBevelButton(NormalBevelButton):
	ProcID = Controls.kControlBevelButtonLargeBevelProc

以上を一つにまとめたまとめたモジュールをbevel.pyで見ることができます。どのように動くか、IDEで"Run as __main__"を設定して実行してみてください。

このボタンクラスを作るには方法がいくつも - 例えばデフォルトのリングを描くときの方法を考慮に入れると - あります。しかし、ここでは独自にウィジェットクラスを作るのに必要とされる手順を説明しました。

(XXX 正しいProcIDを渡したのに、私のコンピュータではSmallBevelButtonLargeBevelButtonNormalBevelButtonと同じに見えます。アピアランスマネージャがどうしたわけか正しく設定していないのでしょうか?)

練習

  1. ControlWidget.__init__()に渡す変数minvaluekControlBehaviorTogglesあるいは kControlBehaviorStickyに変えて、それぞれチェックボックスあるいはラジオボタンのようにふるまうようにしましょう。 RadioButtonCheckBoxのサブクラスを作って、ベベルボタンを作ります。
  2. 他のルーチンを変更する必要のないように、drawfatframe()ルーチンを変えてみましょう。