PyGTKでGUI部品を自前で描画する(後半)
「PyGTKでGUI部品を自前で描画する(前半)」の内容を踏まえた上でのコード例を
- GDKのみ使用
- Cairoを主に使用
の2つそれぞれについて貼り付ける。
ウィンドウサイズを変更すると、それぞれ、大きさの情報を利用した部分については伸びたり縮んだりする。
GDKのみ使用したもの
#! /usr/bin/python # -*- coding: utf-8 -*- import sys import os try: import pygtk pygtk.require('2.0') except: pass try: import pango import gtk except: sys.exit(1) class MyDrawingArea(gtk.DrawingArea): """ 自分で描画するGUI部品 """ def __init__(self): gtk.DrawingArea.__init__(self) self.__gc = None self.__width = self.__height = 0 self.__fontdesc = pango.FontDescription('Monospace 10') self.__layout = self.create_pango_layout('This is a TEST.') self.connect('size-allocate', self.__on_self_size_allocate) self.connect('realize', self.__on_self_realize) self.connect('expose-event', self.__on_self_expose_event) def __on_self_realize(self, widget): """ 一部の初期化 """ print '__on_self_realize()' # GCオブジェクトはこの段階でないと作れない self.__gc = widget.window.new_gc() # 描画に必要な情報を持つ # 線の属性 self.__gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND) # 色 self.__gc.set_rgb_fg_color(gtk.gdk.Color('#cc3366')) # 同様に色の数だけGCオブジェクトを用意 self.__gc2 = widget.window.new_gc() # 描画に必要な情報を持つ self.__gc2.set_rgb_fg_color(gtk.gdk.Color('#336600')) self.__gc3 = widget.window.new_gc() self.__gc3.set_rgb_fg_color(gtk.gdk.Color('#003399')) self.__gc4 = widget.window.new_gc() self.__gc4.set_rgb_fg_color(gtk.gdk.Color('#996633')) def __on_self_size_allocate(self, widget, allocation): """ GUI部品のサイズの変更を受け取る """ print '__on_self_size_allocate()' self.__width = allocation.width self.__height = allocation.height def __on_self_expose_event(self, widget, event): """ 描画を行う 同じ領域に重なる場合、後で描いたほうが上になる """ print '__on_self_expose_event()' self.__fontdesc.set_size(self.__width * 40) self.__layout.set_font_description(self.__fontdesc) # 線を描画 # 2-3番目の引数が始点,4-5番目の引数が終点のX,Y座標 widget.window.draw_line(self.__gc, self.__width / 2, 0, self.__width / 2, self.__height) widget.window.draw_line(self.__gc, 0, self.__height / 2, self.__width, self.__height / 2) # 四角を描画 # 3-4番目の引数が左上の(X,Y),5-6番目の引数が(幅,高さ) # 多角形はdraw_polygon() widget.window.draw_rectangle(self.__gc2, True, self.__width / 2 - 10, self.__height / 2 - 10, 20, 20) # 円(の一部) # 最後の引数は回転角度(64倍すると度単位になる) # 0度が3時の方向で反時計回り widget.window.draw_arc(self.__gc3, True, self.__width / 4, self.__height / 4, self.__width / 2, self.__height / 2, 0, 60 * 64) # 60度 # Pixbuf # 3-4番目がPixbuf内の切り取りの左上(X,Y) # 5-6番目がDrawingArea内の貼り付け位置の左上(X,Y) # 7-8番目がPixbuf内の切り取りの(幅,高さ) # 9-10番目がDrawingArea内の貼り付けの(幅,高さ) -1で元の長さ pixbuf_red = gtk.gdk.pixbuf_new_from_file('/usr/share/pixmaps/apple-green.png') pixbuf_green = gtk.gdk.pixbuf_new_from_file('/usr/share/pixmaps/apple-red.png') widget.window.draw_pixbuf(self.__gc, pixbuf_red, 0, 0, 10, 40, pixbuf_red.props.width / 2, pixbuf_red.props.height, -1, pixbuf_red.props.height / 2) widget.window.draw_pixbuf(self.__gc, pixbuf_green, pixbuf_green.props.width / 2, 0, 10 + pixbuf_red.props.width / 2, 40, pixbuf_green.props.width / 2, pixbuf_green.props.height, -1, pixbuf_red.props.height / 2) # GUI部品の左上を基準にして文字列を描画 # 2-3番目の引数が左上のX,Y座標 widget.window.draw_layout(self.__gc4, 0, 0, self.__layout) # GUI部品の真ん中を基準にして文字列を描画 widget.window.draw_layout(self.__gc4, self.__width / 2, self.__height / 2, self.__layout) class MainWindow(gtk.Window): """ メインウィンドウ """ def __init__(self, *args, **kwargs): gtk.Window.__init__(self, *args, **kwargs) self.props.title = 'PyGTK DrawingArea test (GDK)' self.set_size_request(320, 200) self.__accelgroup = gtk.AccelGroup() self.add_accel_group(self.__accelgroup) self.__item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.__accelgroup) self.__menu_file = gtk.Menu() self.__menu_file.add(self.__item_quit) self.__item_file = gtk.MenuItem('_File') self.__item_file.props.submenu = self.__menu_file self.__menubar = gtk.MenuBar() self.__menubar.append(self.__item_file) self.__drawingarea = MyDrawingArea() self.__vbox = gtk.VBox() self.__vbox.pack_start(self.__menubar, expand=False, fill=False) self.__vbox.pack_start(self.__drawingarea) self.add(self.__vbox) self.__item_quit.connect('activate', gtk.main_quit) self.connect('delete_event', gtk.main_quit) class PyGTKDrawingAreaGDKTest: """ DrawingAreaを用いた描画のテスト(GDKのみ使用) """ def main(self): """ ウィンドウを作成してGTK+のメインループを呼ぶ """ win = MainWindow() win.show_all() gtk.main() if __name__ == '__main__': app = PyGTKDrawingAreaGDKTest() app.main()
Cairoを主に使用したもの
#! /usr/bin/python # -*- coding: utf-8 -*- import math import sys import os try: import pygtk pygtk.require('2.0') except: pass try: import pango import cairo import gtk except: sys.exit(1) class MyDrawingArea(gtk.DrawingArea): """ 自分で描画するGUI部品 """ def __init__(self): gtk.DrawingArea.__init__(self) self.__width = self.__height = 0 self.connect('size-allocate', self.__on_self_size_allocate) self.connect('expose-event', self.__on_self_expose_event) def __on_self_size_allocate(self, widget, allocation): """ GUI部品のサイズの変更を受け取る """ print '__on_self_size_allocate()' self.__width = allocation.width self.__height = allocation.height def __on_self_expose_event(self, widget, event): """ 描画を行う 同じ領域に重なる場合、後で描いたほうが上になる pycairoのContextクラスのリファレンス: http://cairographics.org/documentation/pycairo/reference/context.html#class-context """ print '__on_self_expose_event()' ctx = widget.window.cairo_create() # pycairoのContextクラスを継承したもの ctx.set_source_color(gtk.gdk.Color('#ffffff')) ctx.new_path() # パスの開始 ctx.move_to(0, 0) # 左上に移動 ctx.rel_line_to(self.__width, 0) # 左上から右上へのパス ctx.rel_line_to(0, self.__height) # 右上から右下へのパス ctx.rel_line_to(-1 * self.__width, 0) # 右下から左下へのパス ctx.close_path() # パスを閉じる # 四角を描画する場合はrectangle()を用いるほうが楽 # (new_path()からclose_path()までの流れを置き換える) # ctx.rectangle(0, 0, self.__width, self.__height) # fill()はパス内の塗り潰し・stroke()はパスの輪郭線を描画 ctx.fill() # gtk.gdk.color_parse()から得たgtk.gdk.Colorオブジェクトを用いた色の指定 col = gtk.gdk.color_parse('#ff0000') ctx2 = widget.window.cairo_create() ctx2.set_source_rgb(col.red_float, col.green_float, col.blue_float) # 円(の一部)は3時の方向から時計回り・180度=math.pi # arc_negative()では描画される部分とされない部分が逆だが # 時計回りに変わりはない(反時計回りではない) ctx2.arc(self.__width / 2, self.__height / 2, min(self.__width, self.__height) / 4, 0, 2 * math.pi) ctx2.fill() ctx3 = widget.window.cairo_create() ctx3.set_source_color(gtk.gdk.Color('#ff9999')) ctx3.arc(self.__width / 2, self.__height / 2, min(self.__width, self.__height) / 4, 0, 2 * math.pi) ctx3.stroke() # 輪郭線のみ描画 ctx4 = widget.window.cairo_create() # 直接RGB各成分の値(0.0-1.0)を入れる形の色指定 # set_source_rgba()ではアルファ成分も指定できる ctx4.set_source_rgb(0.3, 0.3, 0.3) # フォントの指定 ctx4.select_font_face('Serif', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx4.set_font_size(32) # 文字のレイアウトでは大きくしたときに崩れない(ズレない)ようにするのは難しい # text_extents()を使うと便利だが、ここでは使わないで描画することにする # 環境によっては真ん中からズレるかもしれない ctx4.move_to(self.__width / 2 - 32 - 64 * self.__width / 100000, self.__height * 15 / 16) # 文字列の描画 ctx4.show_text('日本') # 真ん中上から真ん中下への線 ctx5 = widget.window.cairo_create() ctx5.set_source_rgba(0.1, 0.1, 0.1, 0.25) ctx5.new_path() ctx5.move_to(self.__width / 2, 0) ctx5.rel_line_to(0, self.__height) ctx5.close_path() ctx5.stroke() # 真ん中左から真ん中右への線 ctx6 = widget.window.cairo_create() ctx6.set_source_rgba(0.1, 0.1, 0.1, 0.25) ctx6.new_path() ctx6.move_to(0, self.__height / 2) ctx6.rel_line_to(self.__width, 0) ctx6.close_path() ctx6.stroke() text = '見本' size = min(self.__width, self.__height) / 1.75 ctx7 = widget.window.cairo_create() ctx7.set_source_rgba(0.0, 0.3, 0.9, 0.3) ctx7.select_font_face('Serif', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx7.set_font_size(size) # text_extents()はフォントサイズなどを指定した後で呼ぶ (x_bearing, y_bearing, width, height, x_advance, y_advance) = ctx7.text_extents(text) print 'size:%d, extents:[%d,%d/%d,%d/%d,%d]' % (size, x_bearing, y_bearing, width, height, x_advance, y_advance) # 得られる幅/高さとbearingとを用いることで中央寄せすることができる ctx7.move_to(self.__width / 2 - width / 2 - x_bearing, self.__height / 2 - height / 2 - y_bearing) ctx7.show_text(text) class MainWindow(gtk.Window): """ メインウィンドウ """ def __init__(self, *args, **kwargs): gtk.Window.__init__(self, *args, **kwargs) self.props.title = 'PyGTK DrawingArea test (Cairo)' self.set_size_request(320, 200) self.__accelgroup = gtk.AccelGroup() self.add_accel_group(self.__accelgroup) self.__item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.__accelgroup) self.__menu_file = gtk.Menu() self.__menu_file.add(self.__item_quit) self.__item_file = gtk.MenuItem('_File') self.__item_file.props.submenu = self.__menu_file self.__menubar = gtk.MenuBar() self.__menubar.append(self.__item_file) self.__drawingarea = MyDrawingArea() self.__vbox = gtk.VBox() self.__vbox.pack_start(self.__menubar, expand=False, fill=False) self.__vbox.pack_start(self.__drawingarea) self.add(self.__vbox) self.__item_quit.connect('activate', gtk.main_quit) self.connect('delete_event', gtk.main_quit) class PyGTKDrawingAreaCairoTest: """ DrawingAreaを用いた描画のテスト(Cairo使用) """ def main(self): """ ウィンドウを作成してGTK+のメインループを呼ぶ """ win = MainWindow() win.show_all() gtk.main() if __name__ == '__main__': app = PyGTKDrawingAreaCairoTest() app.main()
関連記事:
使用したバージョン:
- Python 2.6.4
- PyGTK 2.16.0
- pycairo 1.8.6