python cgi教程6——Session

Session是位于服务器端的Cookie。它保存在服务器上的文件或者数据库中。每条session是由session id(SID)进行标识。


基于Cookie的SID


Cookie可以长久胡保存SID,直到Cookie过期。用这种方式更快更安全。但是得客户端的浏览器支持Cookie才行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python

import sha, time, Cookie, os

cookie = Cookie.SimpleCookie()
string_cookie = os.environ.get('HTTP_COOKIE')

# If new session
if not string_cookie:
# The sid will be a hash of the server time
sid = sha.new(repr(time.time())).hexdigest()
# Set the sid in the cookie
cookie['sid'] = sid
# Will expire in a year
cookie['sid']['expires'] = 12 * 30 * 24 * 60 * 60
# If already existent session
else:
cookie.load(string_cookie)
sid = cookie['sid'].value

print cookie
print 'Content-Type: text/html\n'
print '<html><body>'

if string_cookie:
print '<p>Already existent session</p>'
else:
print '<p>New session</p>'

print '<p>SID =', sid, '</p>'
print '</body></html>'

我们对服务器的时间进行哈希生成一个唯一的Session ID。


基于Query String的SID



#!/usr/bin/env python

import sha, time, cgi, os

sid = cgi.FieldStorage().getfirst(‘sid’)

if sid: # If session exists
message = ‘Already existent session’
else: # New session
# The sid will be a hash of the server time
sid = sha.new(repr(time.time())).hexdigest()
message = ‘New session’

qs = ‘sid=’ + sid

print “””\
Content-Type: text/html\n

%s


SID = %s


reload



“”” % (message, sid, sid)

这是将sid直接在url中传递。


使用隐藏域



#!/usr/bin/env python

import sha, time, cgi, os

sid = cgi.FieldStorage().getfirst(‘sid’)

if sid: # If session exists
message = ‘Already existent session’
else: # New session
# The sid will be a hash of the server time
sid = sha.new(repr(time.time())).hexdigest()
message = ‘New session’

qs = ‘sid=’ + sid

print “””\
Content-Type: text/html\n

%s


SID = %s







“”” % (message, sid, sid)

这是将sid放在表单中作为隐藏字段提交。


shelve模块


光有session id是不够的,还需要将内容保存到文件或者数据库中。这里可以使用shelve模块保存到文件。

session = shelve.open('/tmp/.session/sess_' + sid, writeback=True)

它打开文件并返回一个类似于字典的对象。

session['lastvisit'] = repr(time.time())

设置session的值。

lastvisit = session.get('lastvisit')

读取刚刚设置的值。

session.close()

最后操作完成之后要记得关闭文件。


Cookie和Shelve


接下来用一个例子展示下Cookie和Shelve共同使用。

#!/usr/bin/env python
import sha, time, Cookie, os, shelve

cookie = Cookie.SimpleCookie()
string_cookie = os.environ.get('HTTP_COOKIE')

if not string_cookie:
   sid = sha.new(repr(time.time())).hexdigest()
   cookie['sid'] = sid
   message = 'New session'
else:
   cookie.load(string_cookie)
   sid = cookie['sid'].value
cookie['sid']['expires'] = 12 * 30 * 24 * 60 * 60

# The shelve module will persist the session data
# and expose it as a dictionary
session_dir = os.environ['DOCUMENT_ROOT'] + '/tmp/.session'
session = shelve.open(session_dir + '/sess_' + sid, writeback=True)

# Retrieve last visit time from the session
lastvisit = session.get('lastvisit')
if lastvisit:
   message = 'Welcome back. Your last visit was at ' + \
      time.asctime(time.gmtime(float(lastvisit)))
# Save the current time in the session
session['lastvisit'] = repr(time.time())

print """\
%s
Content-Type: text/html\n
<html><body>
<p>%s</p>
<p>SID = %s</p>
</body></html>
""" % (cookie, message, sid)

python cgi教程5——Cookie

设置Cookie

有两个与cookie相关的操作,设置cookie和读取cookie。


以下例子展示了cookie的设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
import time

# This is the message that contains the cookie
# and will be sent in the HTTP header to the client
print 'Set-Cookie: lastvisit=' + str(time.time());

# To save one line of code
# we replaced the print command with a '\n'
print 'Content-Type: text/html\n'
# End of HTTP header

print '<html><body>'
print 'Server time is', time.asctime(time.localtime())
print '</body></html>'

这是在数据头中使用Set-Cookie进行的操作。


检索Cookie


浏览器返回来的cookie存放于os.environ字典中,对应的字段名为’HTTP_COOKIE’。以下是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python
import Cookie, os, time

cookie = Cookie.SimpleCookie()
cookie['lastvisit'] = str(time.time())

print cookie
print 'Content-Type: text/html\n'

print '<html><body>'
print '<p>Server time is', time.asctime(time.localtime()), '</p>'

# The returned cookie is available in the os.environ dictionary
cookie_string = os.environ.get('HTTP_COOKIE')

# The first time the page is run there will be no cookies
if not cookie_string:
print '<p>First visit or cookies disabled</p>'

else: # Run the page twice to retrieve the cookie
print '<p>The returned cookie string was "' + cookie_string + '"</p>'

# load() parses the cookie string
cookie.load(cookie_string)
# Use the value attribute of the cookie to get it
lastvisit = float(cookie['lastvisit'].value)

print '<p>Your last visit was at',
print time.asctime(time.localtime(lastvisit)), '</p>'

print '</body></html>'

使用SimpleCookie对象的load()方法对字符串进行解析。

python cgi教程4——执行Shell命令

可以使用subprocess.Popen或者os.popen4让cgi执行shell命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/python
import cgitb; cgitb.enable()

# The subprocess module is new in 2.4
import os, urllib, subprocess as sub

# Retrieve the command from the query string
# and unencode the escaped %xx chars
str_command = urllib.unquote(os.environ['QUERY_STRING'])

p = sub.Popen(['/bin/bash', '-c', str_command],
stdout=sub.PIPE, stderr=sub.STDOUT)
output = urllib.unquote(p.stdout.read())

print """\
Content-Type: text/html\n
<html><body>
<pre>
$ %s
%s
</pre>
</body></html>
""" % (str_command, output)

注意:这只是一个例子,在生产环境中这么使用是非常的不安全。

可以使用Cookies和Session对用户进行认证以提高安全性。

python cgi教程3——表单

cgi模块中有一个FieldStorage类可用于表单处理。


单一字段名


有一个HTML表单如下:

1
2
3
4
5
6
<html><body>
<form method="get" action="/cgi-bin/form1.py">
Name: <input type="text" name="name">
<input type="submit" value="Submit">
</form>
</body></html>

form1.py内容为:

#!/usr/bin/env python
import cgi
form = cgi.FieldStorage() # instantiate only once!
name = form.getfirst('name', 'empty')

# Avoid script injection escaping the user input
name = cgi.escape(name)

print """\
Content-Type: text/html\n
<html><body>
<p>The submitted name was "%s"</p>
</body></html>
""" % name

getfirst方法获取指定字段的第一个值,如果该字段不存在则为空。将表单的方法改为post它同样适用。


为了避免用户提交危险的内容,可以使用cgi.escape()方法对内容进行转换。


多字段名


对于多个字段具有相同名字的可以使用getlist()方法,它返回一个列表包含了这些值。

<html><body>
<form method="post" action="/cgi-bin/form2.py">
Red<input type="checkbox" name="color" value="red">
Green<input type="checkbox" name="color" value="green">
<input type="submit" value="Submit">
</form>
</body></html>

form2.py内容如下:

#!/usr/bin/env python
import cgi
form = cgi.FieldStorage()

# getlist() returns a list containing the
# values of the fields with the given name
colors = form.getlist('color')

print "Content-Type: text/html\n"
print '<html><body>'
print 'The colors list:', colors
for color in colors:
   print '<p>', cgi.escape(color), '</p>'
print '</body></html>'

文件上传

<html><body>
<form enctype="multipart/form-data" action="/cgi-bin/form3.py" method="post">
<p>File: <input type="file" name="file"></p>
<p><input type="submit" value="Upload"></p>
</form>
</body></html>

getfirst()getlist()都只能获取文件的内容。想获取文件名需要使用FieldStorage


form3.py内容如下:

#!/usr/bin/env python
import cgi, os
import cgitb; cgitb.enable()

try: # Windows needs stdio set for binary mode.
    import msvcrt
    msvcrt.setmode (0, os.O_BINARY) # stdin  = 0
    msvcrt.setmode (1, os.O_BINARY) # stdout = 1
except ImportError:
    pass

form = cgi.FieldStorage()

# A nested FieldStorage instance holds the file
fileitem = form['file']

# Test if the file was uploaded
if fileitem.filename:

   # strip leading path from file name to avoid directory traversal attacks
   fn = os.path.basename(fileitem.filename)
   open('files/' + fn, 'wb').write(fileitem.file.read())
   message = 'The file "' + fn + '" was uploaded successfully'

else:
   message = 'No file was uploaded'

print """\
Content-Type: text/html\n
<html><body>
<p>%s</p>
</body></html>
""" % (message,)

大文件上传

在处理大文件时如果内存不足,可以使用生成器将文件分成小片。

可将之前的脚本改写如下:

#!/usr/bin/env python
import cgi, os
import cgitb; cgitb.enable()

try: # Windows needs stdio set for binary mode.
    import msvcrt
    msvcrt.setmode (0, os.O_BINARY) # stdin  = 0
    msvcrt.setmode (1, os.O_BINARY) # stdout = 1
except ImportError:
    pass

form = cgi.FieldStorage()

# Generator to buffer file chunks
def fbuffer(f, chunk_size=10000):
   while True:
      chunk = f.read(chunk_size)
      if not chunk: break
      yield chunk

# A nested FieldStorage instance holds the file
fileitem = form['file']

# Test if the file was uploaded
if fileitem.filename:

   # strip leading path from file name to avoid directory traversal attacks
   fn = os.path.basename(fileitem.filename)
   f = open('files/' + fn, 'wb', 10000)

   # Read the file in chunks
   for chunk in fbuffer(fileitem.file):
      f.write(chunk)
   f.close()
   message = 'The file "' + fn + '" was uploaded successfully'

else:
   message = 'No file was uploaded'

print """\
Content-Type: text/html\n
<html><body>
<p>%s</p>
</body></html>
""" % (message,)

python cgi教程2——调试

有时数据头出错是很难定位的,除非有权限访问服务器日志。


好在Python有cgitb模块,可以将异常的堆栈信息放在正文中,作为HTML输出。


以下是一个简单的例子:

1
2
3
4
#!/usr/bin/env python
print "Content-Type: text/html\n"
import cgitb; cgitb.enable()
print 1/0

也可以使用handler()方法进行捕获异常处理。

1
2
3
4
5
6
7
8
#!/usr/bin/env python
print "Content-Type: text/html"
print
import cgitb
try:
f = open('non-existent-file.txt', 'r')
except:
cgitb.handler()

还有一种更直接的方法,将数据头设为”text/plain”并把标准错误输出设置到标准输出。

1
2
3
4
5
print "Content-Type: text/plain"
print
import sys
sys.stderr = sys.stdout
f = open('non-existent-file.txt', 'r')

注意:这些只是用于在开发阶段,在生产环境中要把它禁用。以免异常信息被攻击者利用。

python cgi教程1——Hello World

简介

CGI(Common Gateway Interface),通用网关接口的简称。它是客户端和服务器程序进行数据传输的一种标准。


一个CGI程序可以使用任何语言编写,通常它是放在Web服务器(如Apache)目录下的cgi-bin目录里。


实例


接下来看一个简单的例子。

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python
print "Content-Type: text/html"
print
print """\
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>
"""

脚本程序的第一行指定了python解释器的路径。在你系统中它也可能为:

1
2
3
4
#!/usr/bin/python
#!/usr/bin/python2
#!c:\Python26\python.exe
#!c:\Python27\python.exe
1
2
print "Content-Type: text/html"
print

脚本必须输出一个HTTP的头,它由一条或者多条消息构成,然后再一个空行。空行是必需的,它意味着头的结束。

这里我们想要把输出作为HTML解释,因此指定Content-Type为 text/html。


这里也可以写成:

1
print "Content-Type: text/html\n"

保存以上脚本,并添加执行权限。然后在浏览器中访问执行该脚本,应该可以看到”Hello World”这几个字。

[翻译]Ruby GTK教程8——贪吃蛇

贪吃蛇

在这部分的Ruby GTK编程教程中我们将创建一个贪吃蛇游戏。


贪吃蛇是一个比较老的经典电子游戏。它第一次创建是在70年代后期。之后被移植到PC上。在这个游戏中玩家控制蛇,目标是尽可能多的吃掉苹果。蛇每吃掉一个苹果,身体就会变长。必须避免蛇撞到墙或者自己的身体。


开发


蛇的每块关节的大小为10像素。使用方向键控制蛇。初始,蛇有三块关节。游戏立即开始。当游戏结束后在窗口中央显示”Game Over”。


board.rb


WIDTH = 300
HEIGHT = 270
DOT_SIZE = 10
ALL_DOTS = WIDTH HEIGHT / (DOT_SIZE DOT_SIZE)
RAND_POS = 26

$x = [0] ALL_DOTS
$y = [0]
ALL_DOTS

class Board < Gtk::DrawingArea

def initialize
super

modify_bg Gtk::STATE_NORMAL, Gdk::Color.new(0, 0, 0)

signal_connect “expose-event” do
on_expose
end

init_game
end

def on_timer

if @inGame
check_apple
check_collision
move
queue_draw
return true
else
return false
end
end

def init_game

@left = false
@right = true
@up = false
@down = false
@inGame = true
@dots = 3

for i in (0..@dots)
$x[i] = 50 - i 10
$y[i] = 50
end

begin
@dot = Cairo::ImageSurface.from_png “dot.png”
@head = Cairo::ImageSurface.from_png “head.png”
@apple = Cairo::ImageSurface.from_png “apple.png”
rescue Exception => e
puts “cannot load images”
exit
end

locate_apple
GLib::Timeout.add(100) { on_timer }

end


def on_expose

cr = window.create_cairo_context

if @inGame
draw_objects cr
else
game_over cr
end
end

def draw_objects cr

cr.set_source_rgb 0, 0, 0
cr.paint

cr.set_source @apple, @apple_x, @apple_y
cr.paint

for z in (0..@dots)
if z == 0
cr.set_source @head, $x[z], $y[z]
cr.paint
else
cr.set_source @dot, $x[z], $y[z]
cr.paint
end
end
end

def game_over cr

w = allocation.width / 2
h = allocation.height / 2

cr.set_font_size 15
te = cr.text_extents “Game Over”

cr.set_source_rgb 65535, 65535, 65535

cr.move_to w - te.width/2, h
cr.show_text “Game Over”

end


def check_apple

if $x[0] == @apple_x and $y[0] == @apple_y
@dots = @dots + 1
locate_apple
end
end

def move

z = @dots

while z > 0
$x[z] = $x[(z - 1)]
$y[z] = $y[(z - 1)]
z = z - 1
end

if @left
$x[0] -= DOT_SIZE
end

if @right
$x[0] += DOT_SIZE
end

if @up
$y[0] -= DOT_SIZE
end

if @down
$y[0] += DOT_SIZE
end

end

def check_collision

z = @dots

while z > 0
if z > 4 and $x[0] == $x[z] and $y[0] == $y[z]
@inGame = false
end
z = z - 1
end

if $y[0] > HEIGHT - DOT_SIZE
@inGame = false
end

if $y[0] < 0
@inGame = false
end

if $x[0] > WIDTH - DOT_SIZE
@inGame = false
end

if $x[0] < 0
@inGame = false
end

end

def locate_apple

r = rand(RAND_POS)
@apple_x = r
DOT_SIZE
r = rand(RAND_POS)
@apple_y = r DOT_SIZE
end

def on_key_down event

key = event.keyval

if key == Gdk::Keyval::GDK_Left and not @right
@left = true
@up = false
@down = false
end

if key == Gdk::Keyval::GDK_Right and not @left
@right = true
@up = false
@down = false
end

if key == Gdk::Keyval::GDK_Up and not @down
@up = true
@right = false
@left = false
end

if key == Gdk::Keyval::GDK_Down and not @up
@down = true
@right = false
@left = false
end
end
end

首先我们定义一些全局变量。


WIDTH和HEIGHT常量决定了甲板的大小。DOT_SIZE是苹果和蛇的每个点的大小。ALL_DOTS常量定义了甲板可能包含的最大的点数量。RAND_POS常量用于计算苹果的随机位置。DELAY常量决定游戏速度。


$x = [0]  ALL_DOTS
$y = [0] ALL_DOTS

这两个数组存储了蛇所有关节的x、y坐标。


init_game方法初始化变量、加载图片和启动timeout函数。


if @inGame
draw_objects cr
else
game_over cr
end

on_expose_method方法里我们检查@inGame变量。如果为true,绘制苹果和蛇。否则显示”Game over”文字。


def draw_objects cr

cr.set_source_rgb 0, 0, 0
cr.paint

cr.set_source @apple, @apple_x, @apple_y
cr.paint

for z in (0..@dots)
if z == 0
cr.set_source @head, $x[z], $y[z]
cr.paint
else
cr.set_source @dot, $x[z], $y[z]
cr.paint
end
end
end

draw_objects方法绘制苹果和蛇。蛇的头部用红色的圆表示。


def check_apple

if $x[0] == @apple_x and $y[0] == @apple_y
@dots = @dots + 1
locate_apple
end
end

check_apple方法查检蛇是否碰到苹果,如果是则增加蛇的关节并调用locate_apple方法随机放置一个新的苹果。


move方法是游戏的关键算法。为了理解它,先看一下蛇是如何移动的。控制蛇头,可以使用方向键改变它的方向。其余的关节朝该方向前进。第二个关节移到到第一关节的位置,第三个关节到第二个等等。


while z > 0
$x[z] = $x[(z - 1)]
$y[z] = $y[(z - 1)]
z = z - 1
end

这些代码将关节按照链状前进。


if @left
$x[0] -= DOT_SIZE
end

头部向左移动。


check_collision方法中,我们检查蛇是否撞到了自己或者墙。


while z > 0
if z > 4 and $x[0] == $x[z] and $y[0] == $y[z]
@inGame = false
end
z = z - 1
end

如果蛇撞到了自己,游戏结束。


if $y[0] > HEIGHT - DOT_SIZE
@inGame = false
end

如果蛇撞到底部,游戏结束。


localte_apple方法在甲板上随机定位一个苹果。


r = rand(RAND_POS)

获取0到RAND_POS-1的一个随机数。


@apple_x = r  DOT_SIZE

@apple_y = r * DOT_SIZE

这几行设置了苹果的x、y坐标。


if @inGame
check_apple
check_collision
move
queue_draw
return true
else
return false
end

第140ms调用一次on_timer方法。如果游戏运行则调用三个组成游戏逻辑的方法。否则返回false,停止定时事件。


在Board类的on_key_down方法中我们判断按下的键。


if key == Gdk::Keyval::GDK_Left and not @right
@left = true
@up = false
@down = false
end

如果我们按的是左方向键,我们设置left变量为true。这个变量用于move方法改变蛇的坐标。同样注意,当蛇是朝右时,我们不能立即朝左。


nibbles.rb


#!/usr/bin/ruby

# ZetCode Ruby GTK tutorial
#
# This is a simple nibbles game
# clone
#
# author: jan bodnar
# website: www.zetcode.com
# last modified: June 2009

require ‘gtk2’
require ‘board’

class RubyApp > Gtk::Window
def initialize
super

set_title “Nibbles”
signal_connect “destroy” do
Gtk.main_quit
end

@board = Board.new
signal_connect “key-press-event” do |w, e|
on_key_down(w, e)
end

add @board

set_default_size 300, 270
set_window_position Gtk::Window::POS_CENTER
show_all
end

def on_key_down widget, event

key = event.keyval
@board.on_key_down event
end
end

Gtk.init
window = RubyApp.new
Gtk.main

在这个类我们启动了贪吃游戏。


def on_key_down widget, event

key = event.keyval
@board.on_key_down event
end

在这个类的捕获按键事件然后委托Board类的on_key_down method方法进行处理。


image

图片:贪吃蛇


这是使用Ruby语言和GTK库编写的贪吃蛇计算机游戏。




原文地址: http://zetcode.com/gui/rubygtk/nibbles/

翻译:龙昌 admin@longchangjin.cn

[翻译]Ruby GTK教程7——自定义控件

自定义控件

大多数套件通常只提供了最常用的控件,如按钮、文本控件、滑动条等。没有套件可以提供所有可能的控件。程序员必须自己创建这些。这是通过套件提供的绘制工具完成。这有两种可能。程序员可以修改或增强已存在的控件,或者从头开始创建一个自定义控件。


Burning控件


这个例子我们从头开始创建一个控件。这个控件可以在各种媒体烧定应用中看到,如Nero Burning ROM。


custom.rb


#!/usr/bin/ruby

# ZetCode Ruby GTK tutorial
#
# This example creates a burning
# custom widget
#
# author: jan bodnar
# website: zetcode.com
# last edited: June 2009


require ‘gtk2’

class Burning < Gtk::DrawingArea

def initialize(parent)
@parent = parent

super()

@num = [ “75”, “150”, “225”, “300”,
“375”, “450”, “525”, “600”, “675” ]

set_size_request 1, 30
signal_connect “expose-event” do
expose
end
end


def expose

cr = window.create_cairo_context
draw_widget cr

end

def draw_widget cr

cr.set_line_width 0.8

cr.select_font_face(“Courier”,
Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_NORMAL)
cr.set_font_size 11

width = allocation.width

@cur_width = @parent.get_cur_value

step = (width / 10.0).round

till = (width / 750.0) @cur_width
full = (width / 750.0)
700

if @cur_width >= 700

cr.set_source_rgb(1.0, 1.0, 0.72)
cr.rectangle(0, 0, full, 30)
cr.clip
cr.paint
cr.reset_clip

cr.set_source_rgb(1.0, 0.68, 0.68)
cr.rectangle(full, 0, till-full, 30)
cr.clip
cr.paint
cr.reset_clip

else
cr.set_source_rgb 1.0, 1.0, 0.72
cr.rectangle 0, 0, till, 30
cr.clip
cr.paint
cr.reset_clip
end


cr.set_source_rgb(0.35, 0.31, 0.24)

for i in (1..@num.length)
cr.move_to istep, 0
cr.line_to i
step, 5
cr.stroke

te = cr.text_extents @num[i-1]
cr.move_to istep-te.width/2, 15
cr.text_path @num[i-1]
cr.stroke
end
end
end


class RubyApp < Gtk::Window
def initialize
super

set_title “Burning”
signal_connect “destroy” do
Gtk.main_quit
end

set_size_request 350, 200
set_window_position Gtk::Window::POS_CENTER

@cur_value = 0

vbox = Gtk::VBox.new false, 2

scale = Gtk::HScale.new
scale.set_range 0, 750
scale.set_digits 0
scale.set_size_request 160, 35
scale.set_value @cur_value

scale.signal_connect “value-changed” do |w|
on_changed(w)
end

fix = Gtk::Fixed.new
fix.put scale, 50, 50

vbox.pack_start fix

@burning = Burning.new(self)
vbox.pack_start @burning, false, false, 0

add vbox
show_all
end

def on_changed widget

@cur_value = widget.value
@burning.queue_draw
end

def get_cur_value
return @cur_value
end
end

Gtk.init
window = RubyApp.new
Gtk.main

我们将DrawingArea放在窗口的底部,并且手动绘制控件的条目。所有的重要代码放在draw_widget里,通过Burning类的expose方法调用。这个控件生动的显示了媒介的容量和剩余空间。这个控件通过刻度控件来控制。我们自定义控件的最小值为0,最大值为750。如果达到700,我们开始绘制红色。这通常表明超标了。


@num = [ “75”, “150”, “225”, “300”,
“375”, “450”, “525”, “600”, “675” ]

这些数字显示在控件上。他们显示了媒介的容量。


@cur_width = @parent.get_cur_value

通过父控件我们获得刻度控件的值。


till = (width / 750.0)  @cur_width
full = (width / 750.0) 700

我们使用width变量进行刻度值和自定义控件尺寸的转换。注意我们使用了浮点数,得到较大精度的绘制。till参数决定了绘制的总大小,它的值来自刻度控件。它是整个区域的比例。full参数决定了从什么位置开始绘制红色。


cr.set_source_rgb(1.0, 1.0, 0.72)
cr.rectangle(0, 0, full, 30)
cr.clip
cr.paint
cr.reset_clip

绘制黄色矩形直到full点。


te = cr.text_extents @num[i-1]
cr.move_to i
step-te.width/2, 15
cr.text_path @num[i-1]
cr.stroke

这些代码绘制了burning控件的数字。我们计算了文本恰当的位置。


def on_changed widget

@cur_value = widget.value
@burning.queue_draw
end

我们获取刻度控件的值保存在cur_value变量中,稍后使用。重绘burning控件。


image

图片:Burning widget


在这一章中,我们创建了一个自定义控件。




原文地址: http://zetcode.com/gui/rubygtk/customwidget/

翻译:龙昌 admin@longchangjin.cn

[翻译]Ruby GTK教程6——Cairo

使用Cairo绘制

这部分的Ruby GTK教程,我们将使用Cairo库进行一些绘制。


Cairo是一个用于创建2D矢量图像的库。我们可以用它来绘制自己的控件、图表或者各种效果或动画。


颜色


在第一个会例子,我们将介绍颜色。颜色是一个代表了红、绿和蓝(RGB)强度值的对象。Cairo的RGB有效值范围为0到1。


#!/usr/bin/ruby

# ZetCode Ruby GTK tutorial
#
# This program shows how to work
# with colors in Cairo
#
# author: jan bodnar
# website: www.zetcode.com
# last modified: June 2009

require ‘gtk2’


class RubyApp < Gtk::Window

def initialize
super

set_title “Colors”
signal_connect “destroy” do
Gtk.main_quit
end

init_ui

set_default_size 360, 100
set_window_position Gtk::Window::POS_CENTER

show_all
end

def init_ui

@darea = Gtk::DrawingArea.new

@darea.signal_connect “expose-event” do
on_expose
end

add(@darea)

end

def on_expose

cr = @darea.window.create_cairo_context
draw_colors cr

end

def draw_colors cr

cr.set_source_rgb 0.2, 0.23, 0.9
cr.rectangle 10, 15, 90, 60
cr.fill

cr.set_source_rgb 0.9, 0.1, 0.1
cr.rectangle 130, 15, 90, 60
cr.fill

cr.set_source_rgb 0.4, 0.9, 0.4
cr.rectangle 250, 15, 90, 60
cr.fill
end
end

Gtk.init
window = RubyApp.new
Gtk.main

这个例子中我们绘制了三个矩形并且用三种不同的颜色填充。


@darea = Gtk::DrawingArea.new

我们将在DrawingArea控件是进行绘制操作。


@darea.signal_connect “expose-event” do
on_expose
end

当窗口需要重绘时expose-event事件将触发。对这个事件的响应中我们调用了on_expose方法。


cr = @darea.window.create_cairo_context

从GdkWindow创建cairo上下文对象。这个上下文是我们将要进行所有绘制的对象。


draw_colors cr

实际的绘制委托给draw_colors方法。


cr.set_source_rgb 0.2, 0.23, 0.9

set_source_rgb方法是设置cairo上下文的颜色。这个方法的三个参数是颜色的强度值。


cr.rectangle 10, 15, 90, 60

绘制一个矩形。前两个参数是矩形左上角的x、y坐标。后两个参数是矩形的宽和高。


cr.fill

使用当前的颜色填充矩形。


image1

图片:颜色


基本形状


下一个例子在窗口上绘制一些基本形状。


#!/usr/bin/ruby

# ZetCode Ruby GTK tutorial
#
# This code example draws basic shapes
# with the Cairo library
#
# author: jan bodnar
# website: www.zetcode.com
# last modified: June 2009

require ‘gtk2’


class RubyApp < Gtk::Window

def initialize
super

set_title “Basic shapes”
signal_connect “destroy” do
Gtk.main_quit
end

init_ui

set_default_size 390, 240
set_window_position Gtk::Window::POS_CENTER

show_all
end

def init_ui

@darea = Gtk::DrawingArea.new

@darea.signal_connect “expose-event” do
on_expose
end

add(@darea)
end

def on_expose

cr = @darea.window.create_cairo_context
draw_shapes cr
end

def draw_shapes cr

cr.set_source_rgb 0.6, 0.6, 0.6

cr.rectangle 20, 20, 120, 80
cr.rectangle 180, 20, 80, 80
cr.fill

cr.arc 330, 60, 40, 0, 2Math::PI
cr.fill

cr.arc 90, 160, 40, Math::PI/4, Math::PI
cr.fill

cr.translate 220, 180
cr.scale 1, 0.7
cr.arc 0, 0, 50, 0, 2
Math::PI
cr.fill
end
end

Gtk.init
window = RubyApp.new
Gtk.main

这个例子我们将创建一个矩形、方形、圆形、弧形和椭圆形。我们将轮廓绘为蓝色,内部为白色。


cr.rectangle 20, 20, 120, 80
cr.rectangle 180, 20, 80, 80
cr.fill

这几行绘制了一个矩形和一个方形。


cr.arc 330, 60, 40, 0, 2Math::PI
cr.fill

arc方法绘制一个全圆。


cr.translate 220, 180
cr.scale 1, 0.7
cr.arc 0, 0, 50, 0, 2
Math::PI
cr.fill

translate方法将对象移动到指定的点。如果我们想要绘制椭圆,我们需要进行一些缩放。这里scale方法将y轴收缩。


image2

图片:基本形状


透明矩形


透明度是透过实体的可见度。最简单的理解可以把它想象成玻璃或者水。光线可以透过玻璃,这样我们就可以看到玻璃后的物体。


在计算机图像中,我们可以使用透明混合实现透明度。透明混合处理图片和背景的组合,显示部透明。作品处理使用了阿尔法通道。


#!/usr/bin/ruby

# ZetCode Ruby GTK tutorial
#
# This program shows transparent
# rectangles using Cairo
#
# author: jan bodnar
# website: www.zetcode.com
# last modified: June 2009

require ‘gtk2’


class RubyApp < Gtk::Window

def initialize
super

set_title “Transparent rectangles”
signal_connect “destroy” do
Gtk.main_quit
end

init_ui

set_default_size 590, 90
set_window_position Gtk::Window::POS_CENTER

show_all
end

def init_ui

@darea = Gtk::DrawingArea.new

@darea.signal_connect “expose-event” do
on_expose
end

add(@darea)

end

def on_expose

cr = @darea.window.create_cairo_context

for i in (1..10)
cr.set_source_rgba 0, 0, 1, i0.1
cr.rectangle 50
i, 20, 40, 40
cr.fill
end
end
end

Gtk.init
window = RubyApp.new
Gtk.main

这个例子我们使用不同等级透明度绘制了10个矩形。


cr.set_source_rgba 0, 0, 1, i0.1

set_source_rgba方法是最后一个参数是alpha透明度。


image3

图片:透明矩形


甜甜圈


接下来的例子我们通过旋转一堆椭圆来创建一个复杂的形状。


#!/usr/bin/ruby

# ZetCode Ruby GTK tutorial
#
# This program creates a donut
# with Cairo library
#
# author: jan bodnar
# website: www.zetcode.com
# last modified: June 2009

require ‘gtk2’


class RubyApp < Gtk::Window

def initialize
super

set_title “Donut”
signal_connect “destroy” do
Gtk.main_quit
end

init_ui

set_default_size 350, 250
set_window_position Gtk::Window::POS_CENTER

show_all
end

def init_ui

@darea = Gtk::DrawingArea.new

@darea.signal_connect “expose-event” do
on_expose
end

add(@darea)

end

def on_expose

cr = @darea.window.create_cairo_context
cr.set_line_width 0.5

w = allocation.width
h = allocation.height

cr.translate w/2, h/2
cr.arc 0, 0, 120, 0, 2
Math::PI
cr.stroke

for i in (1..36)
cr.save
cr.rotate iMath::PI/36
cr.scale 0.3, 1
cr.arc 0, 0, 120, 0, 2
Math::PI
cr.restore
cr.stroke
end
end
end

Gtk.init
window = RubyApp.new
Gtk.main

这个例子我们创建了一个甜甜圈。它的形状与饼干相似,因此取名为甜甜圈(donut)。


cr.translate w/2, h/2
cr.arc 0, 0, 120, 0, 2Math::PI
cr.stroke

在开始只是一个椭圆。


for i in (1..36)
cr.save
cr.rotate i
Math::PI/36
cr.scale 0.3, 1
cr.arc 0, 0, 120, 0, 2*Math::PI
cr.restore
cr.stroke
end

经过一些旋转后变成了甜甜圈。


image4

图片:Donut


绘制文本


下一个例子我们在窗口上绘制一些文本。


#!/usr/bin/ruby

# ZetCode Ruby GTK tutorial
#
# This program draws text
# using Cairo
#
# author: jan bodnar
# website: www.zetcode.com
# last modified: June 2009

require ‘gtk2’


class RubyApp < Gtk::Window

def initialize
super

set_title “Soulmate”
signal_connect “destroy” do
Gtk.main_quit
end

init_ui

set_default_size 370, 240
set_window_position Gtk::Window::POS_CENTER

show_all
end

def init_ui

@darea = Gtk::DrawingArea.new

@darea.signal_connect “expose-event” do
on_expose
end

add(@darea)

end

def on_expose

cr = @darea.window.create_cairo_context

cr.set_source_rgb 0.1, 0.1, 0.1

cr.select_font_face “Purisa”, Cairo::FONT_SLANT_NORMAL,
Cairo::FONT_WEIGHT_NORMAL
cr.set_font_size 13

cr.move_to 20, 30
cr.show_text “Most relationships seem so transitory”
cr.move_to 20, 60
cr.show_text “They’re all good but not the permanent one”
cr.move_to 20, 120
cr.show_text “Who doesn’t long for someone to hold”
cr.move_to 20, 150
cr.show_text “Who knows how to love without being told”
cr.move_to 20, 180
cr.show_text “Somebody tell me why I’m on my own”
cr.move_to 20, 210
cr.show_text “If there’s a soulmate for everyone”
end
end

Gtk.init
window = RubyApp.new
Gtk.main

我们显示了Natasha Bedingfields Soulmate歌的部分歌词。


cr.select_font_face “Purisa”, Cairo::FONT_SLANT_NORMAL,
Cairo::FONT_WEIGHT_NORMAL

这里我们指定我们使用的字体。


cr.set_font_size 13

我们指定字体的大小。


cr.move_to 20, 30

移动到开始绘制文本的坐标。


cr.show_text “Most relationships seem so transitory”

show_text方法在窗口上绘制文本。


image5

图片: Soulmate


这章的Ruby GTK教程我们使用Cairo库进行绘制。




原文地址: http://zetcode.com/gui/rubygtk/cairo/

翻译:龙昌 admin@longchangjin.cn

[翻译]Ruby GTK教程5——对话框

对话框

在这部分的Ruby GTK教程我们将介绍对话框。


对话框是现代GUI应用程序不可缺的一部分。对话是两个或者更多人之间交谈。在计算机程序中对话是一个用于与应用程序交互的窗口。对话框用于输入数据、修改数据、修改设置等。对话框在用户与计算机程序之间的交流具有重要意义。


消息盒子


消息对话框是应用程序便于给用户提供消息的对话框。消息由文本和图片数据组成。


#!/usr/bin/ruby

# ZetCode Ruby GTK tutorial
#
# This example shows message
# dialogs
#
# author: jan bodnar
# website: www.zetcode.com
# last modified: June 2009

require ‘gtk2’


class RubyApp < Gtk::Window

def initialize
super

set_title “Messages”
signal_connect “destroy” do
Gtk.main_quit
end

init_ui

set_default_size 250, 100
set_window_position Gtk::Window::POS_CENTER

show_all
end

def init_ui

table = Gtk::Table.new 2, 2, true

info = Gtk::Button.new “Information”
warn = Gtk::Button.new “Warning”
ques = Gtk::Button.new “Question”
erro = Gtk::Button.new “Error”

info.signal_connect “clicked” do
on_info
end

warn.signal_connect “clicked” do
on_warn
end

ques.signal_connect “clicked” do
on_ques
end

erro.signal_connect “clicked” do
on_erro
end

table.attach info, 0, 1, 0, 1
table.attach warn, 1, 2, 0, 1
table.attach ques, 0, 1, 1, 2
table.attach erro, 1, 2, 1, 2

add table

end

def on_info
md = Gtk::MessageDialog.new(self,
Gtk::Dialog::DESTROY_WITH_PARENT, Gtk::MessageDialog::INFO,
Gtk::MessageDialog::BUTTONS_CLOSE, “Download completed”)
md.run
md.destroy
end


def on_erro
md = Gtk::MessageDialog.new(self, Gtk::Dialog::MODAL |
Gtk::Dialog::DESTROY_WITH_PARENT, Gtk::MessageDialog::ERROR,
Gtk::MessageDialog::BUTTONS_CLOSE, “Error loading file”)
md.run
md.destroy
end


def on_ques
md = Gtk::MessageDialog.new(self,
Gtk::Dialog::DESTROY_WITH_PARENT, Gtk::MessageDialog::QUESTION,
Gtk::MessageDialog::BUTTONS_CLOSE, “Are you sure to quit?”)
md.run
md.destroy
end

def on_warn
md = Gtk::MessageDialog.new(self,
Gtk::Dialog::DESTROY_WITH_PARENT, Gtk::MessageDialog::WARNING,
Gtk::MessageDialog::BUTTONS_CLOSE, “Unallowed operation”)
md.run
md.destroy
end
end

Gtk.init
window = RubyApp.new
Gtk.main

我们的例子中显示了四种消息对话框。信息、警告、询问和错误消息对话框。


info = Gtk::Button.new “Information”
warn = Gtk::Button.new “Warning”
ques = Gtk::Button.new “Question”
erro = Gtk::Button.new “Error”

创建四个按钮。每个按钮将显示不同种类的消息对话框。


def on_info
md = Gtk::MessageDialog.new(self,
Gtk::Dialog::DESTROY_WITH_PARENT, Gtk::MessageDialog::INFO,
Gtk::MessageDialog::BUTTONS_CLOSE, “Download completed”)
md.run
md.destroy
end

如果我们点击了info按钮,信息对话框将显示。Gtk::MessageDialog::INFO指定了对话框的类型。Gtk::MessageDialog::BUTTONS_CLOSE指定在对话框中显示的按钮。最后一个参数是要显示的消息。对话框使用run方法显示。程序员必须也要调用destroy或者hide方法。

image1 image2

image3 image4


关于对话框(AboutDialog)


关于对话框显示了应用程序的信息。关于对话框可以显示logo、应用程序名、版本号、版权、网站或者授权信息。它也可能给出作者、文档编写才、翻译者和设计师的信息。


#!/usr/bin/ruby

# ZetCode Ruby GTK tutorial
#
# This example demonstrates the
# AboutDialog dialog
#
# author: jan bodnar
# website: www.zetcode.com
# last modified: June 2009

require ‘gtk2’


class RubyApp < Gtk::Window

def initialize
super

set_title “About dialog”
signal_connect “destroy” do
Gtk.main_quit
end

init_ui

set_default_size 300, 150
set_window_position Gtk::Window::POS_CENTER

show_all
end

def init_ui

button = Gtk::Button.new “About”
button.set_size_request 80, 30

button.signal_connect “clicked” do
on_clicked
end

fix = Gtk::Fixed.new
fix.put button, 20, 20

add fix

end

def on_clicked
about = Gtk::AboutDialog.new
about.set_program_name “Battery”
about.set_version “0.1”
about.set_copyright “(c) Jan Bodnar”
about.set_comments “Battery is a simple tool for battery checking”
about.set_website “http://www.zetcode.com
about.set_logo Gdk::Pixbuf.new “battery.png”
about.run
about.destroy
end
end

Gtk.init
window = RubyApp.new
Gtk.main

代码伅使用了AboutDialog的一些特性。


about = Gtk::AboutDialog.new

创建一个AboutDialog。


about.set_program_name “Battery”
about.set_version “0.1”
about.set_copyright “(c) Jan Bodnar”

这里我们指定名称、版本号和版权信息。


about.set_logo Gdk::Pixbuf.new “battery.png”

这行创建一个logo。

image5

图片:AboutDialog


字体选择对话框


FontSelectionDialog是一个用于选择字体的对话框。它典型的应用于文本编辑或者格式化的应用程序中。


#!/usr/bin/ruby

# ZetCode Ruby GTK tutorial
#
# This example works with the
# FontSelectionDialog
#
# author: jan bodnar
# website: www.zetcode.com
# last modified: June 2009

require ‘gtk2’


class RubyApp < Gtk::Window

def initialize
super

set_title “FontSelectionDialog”
signal_connect “destroy” do
Gtk.main_quit
end

init_ui

set_default_size 300, 150
set_window_position Gtk::Window::POS_CENTER

show_all
end

def init_ui

set_border_width 10
@label = Gtk::Label.new “The only victory over love is flight.”
button = Gtk::Button.new “Select font”

button.signal_connect “clicked” do
on_clicked
end

fix = Gtk::Fixed.new
fix.put button, 100, 30
fix.put @label, 30, 90
add fix

end

def on_clicked
fdia = Gtk::FontSelectionDialog.new “Select font name”
response = fdia.run

if response == Gtk::Dialog::RESPONSE_OK
font_desc = Pango::FontDescription.new fdia.font_name
if font_desc
@label.modify_font font_desc
end
end
fdia.destroy
end
end

Gtk.init
window = RubyApp.new
Gtk.main

这个代码例子我们创建了一个按钮和一个标签。点击按钮之后显示字体选择对话框。


fdia = Gtk::FontSelectionDialog.new “Select font name”

创建FontSelectionDialog


if response == Gtk::Dialog::RESPONSE_OK
font_desc = Pango::FontDescription.new fdia.font_name
if font_desc
@label.modify_font font_desc
end
end

如果点击确定按钮,标签的字体会变为我们在对话框中选中的。

image6

图片:FontSelectionDialog


颜色选择对话框


ColorSelectionDialog是用于选择颜色的对话框。


#!/usr/bin/ruby

# ZetCode Ruby GTK tutorial
#
# This example works with the
# ColorSelectionDialog
#
# author: jan bodnar
# website: www.zetcode.com
# last modified: June 2009

require ‘gtk2’


class RubyApp < Gtk::Window

def initialize
super

set_title “ColorSelectionDialog”
signal_connect “destroy” do
Gtk.main_quit
end

init_ui

set_default_size 350, 150
set_window_position Gtk::Window::POS_CENTER

show_all
end

def init_ui

set_border_width 10
@label = Gtk::Label.new “The only victory over love is flight.”
button = Gtk::Button.new “Select color”

button.signal_connect “clicked” do
on_clicked
end

fix = Gtk::Fixed.new
fix.put button, 100, 30
fix.put @label, 30, 90
add fix
end

def on_clicked
cdia = Gtk::ColorSelectionDialog.new “Select color”
response = cdia.run

if response == Gtk::Dialog::RESPONSE_OK
colorsel = cdia.colorsel
color = colorsel.current_color
@label.modify_fg Gtk::STATE_NORMAL, color
end

cdia.destroy
end
end

Gtk.init
window = RubyApp.new
Gtk.main

这个例子与前一个例子很相似。这次我们是改变标签的颜色。


cdia = Gtk::ColorSelectionDialog.new “Select color”

创建ColorSelectionDialog。


if response == Gtk::Dialog::RESPONSE_OK
colorsel = cdia.colorsel
color = colorsel.current_color
@label.modify_fg Gtk::STATE_NORMAL, color
end

如果按下OK按钮,我们得到颜色值并修改标签的颜色。


image7

图片:ColorSelectionDialog


这部分的Ruby GTK教程我们展示了对话框。




原文地址: http://zetcode.com/gui/rubygtk/dialogs/

翻译:龙昌 admin@longchangjin.cn