Friday, 13 March 2009

写Musorg程序使用QT4笔记

介绍

最近用写Musorg程序的机会学了一下PyQT4编程,感觉很不错,不过也遇到了不少的问题。在这里为以后做一些记录吧。

QT4 Designer

QT4 Designer为界面的设计提供了非常大的帮助,并且在设计的同时,他能够生成相应的C++代码。不过,我编程一般都会用Python,所以希望能够直接调用设计好的界面。在PyGTK中,我们有pygtk.glade这个模块,能够动态加载GTK设计的XML文件。幸运的是,QT4也提供了动态加载的模块,但是更进一步的是,他提供了一个将设计文件转成Python代码的工具:pyuic4。
该工具,pyuic4的基本是用方法就是:pyuic4 -o <python_file> <qt_ui_file>。选项-o就是 告诉pyuic4应该将生成的Python代码输出到那个文件中。这样一来,我们就可以直接在主程序 中调用相应的UI模块了。至于命名,pyuic4会根据QT4 Designer中最上层窗口的对象名,在其 之前加上UI。

调用生成的代码

如果要调用生成的代码的话,可以用如下的方法:
from ui_api import UI_MusorgMainWindow

class MusorgUI(QtGui.MainWindow, UI_MusorgMainWindow)
    def __init__(self):
        QtGui.MainWindow.__init__(self)
        self.setupUi(self)
        ...

QT4 Layout

在用Designer设计的时候,Layout一直困惑了我一阵。我以为,像GTK一样,QT4直接将Layouts里 边任意一个布局拖进控件里就行。结果发现每次拖进去,布局都没有自动展开,而只是被置于拖进 时的位置,而且大小是要手动调整,这显然不对。折腾了一阵后发现,原来是直接在对象查看器里 找到相应的容器控件,右击,然后选择布局便可。

QT4的信号发送与处理

QT4的信号发送与处理比较显而易见,所以没有遇到太大的问题。和GTK的信号发送与处理类似,也 是通过emit和connect(虽然函数名不太一样)。PyQT4中信号发送如下:
...
self.emit(SIGNAL("dataChanged(int, double)"), int_number, float_number)
...
假设之前这个信号是有一个叫做acontrol的QT4控件发送出来的,那么我们可以在另一个控件中定义 接受和处理该信号的函数:
def on_data_changed(self, int_param, float_param):
    # process the data
    pass
有了该函数之后,我们将信号与函数之间建立链接:
...
self.connect(acontrol, SIGNAL("dataChanged(int, double)"), self.on_data_changed)
...
这样就建立了信号的发送与接收的代码。每当程序执行到emit语句时,就会去查找相应的处理函数。

TreeView笔记

TreeView、ListView、TableView也许是任何一个UI库里边都比较复杂的控件。随着MVC模式的推广, 大部分UI库都在原有的Item-based控件之外又提供了Model-based控件。QT4里边的TreeView、ListView 和TableView就是Model-based,程序里我用的是TreeView。
QT4做的好的一点是的确将M,V,C分开了。M一般是由继承于QAbstractItemModel的类来控制,该类 需要实现以下函数:
  1. data(index, role): 返回对应于index的数据,需要注意的是,该函数在默认情况下应该只有在 role为Qt.DisplayRole的情况下才返回数据,其他时候应该返回空数据(QVariant())。
  2. rowCount(parent): 返回以parent为父节点的节点行数。
  3. index(row, column, parent): 返回以parent为父节点,row行column列的节点的索引。
  4. columnCount(parent): 返回以parent为父节点的节点列数。
  5. parent(index): 返回index索引所对应的父节点,如果没有父节点,返回空索引(QModelIndex())。
V一般是由继承于QAbstractItemDelegate的类来实现的,一般会重载paint()函数来绘画字符串以外 的内容,例如进度条、选项框、按钮等等。
C就是QTreeView本身。当用户选中某一个节点时,TreeView会发射信号,这就是控制器的一种表现。

QThread多线程笔记

刚开始用QThread时,不太习惯,而且犯了一个非常大的错误。该错误在今后的编程中也一定要注意。 其实是因为不知道要这么做,就是每一个QThread就是一个QObject,和其他QObject一样,应该有一个 parent,如果没有,当线程结束时(run函数返回时),它就会抛出异常:“Thread deleted while it's running”。

Semaphore, Mutex, Wait Condition

这三个其实就是我们在进程调用和通信中常用的,没有太大区别。当有共享资源需要控制时,就会需要 用到这些。但是这些还可以用来控制开始/暂停,呵呵~~
用这些来实现开始/暂停的原理其实比较简单:
  1. 让暂停信号(pause_semaphore)拥有一个资源。
  2. 当运行时,程序检测暂停信号资源的个数,如果大于零,那么程序会正常运行
  3. 暂停时,我们获取一个暂停信号资源,这时程序发现资源数不够,就会停止。
  4. 当我们再次开始时,我们释放一个信号资源,这样程序就会继续运行了。
还要注意的是,QThread不能直接控制主程序中的数据(其实也不推荐这么做),所以可以通过发送 信号给主程序,让接受到信号的主程序来做相应的处理。

Monday, 2 March 2009

发Blogspot文章的Python程序

初衷

因为是技术博客,所以经常有代码。每次发篇有代码的博客,都要先将代码用Pygments转成HTML,然后将HTML拷到博客的HTML中,再继续编辑博客。这样 麻烦死了。再者,在网上编辑格式还是不如Python的RST直接写出来的方便,所以就直接用Python的RST来写博客文章,然后通过Google Blogger API发到 网上,这样方便多了。

这篇文章就是第一次采用blogspot这个程序来完成编辑、渲染和上传。

代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# utils.py
# author: rshen
# date: 2009-02-27

from gdata import service
import gdata
import atom

def post_blogspot(title, html_content, username, password):
    blogger_service = service.GDataService(username, password)
    blogger_service.source = 'blogup-app-1.0'
    blogger_service.service = 'blogger'
    blogger_service.account_type = 'GOOGLE'
    blogger_service.server = 'www.blogger.com'
    blogger_service.ProgrammaticLogin()

    entry = gdata.GDataEntry()
    entry.title = atom.Title("xhtml", title)
    entry.content = atom.Content(content_type='html',
            text=html_content.decode("utf-8"))

    query = service.Query()
    query.feed = '/feeds/default/blogs'
    feed = blogger_service.Get(query.ToUri())
    blog_id = feed.entry[0].GetSelfLink().href.split("/")[-1]

    return blogger_service.Post(entry, '/feeds/%s/posts/default' % blog_id)
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# blogup.py
# author: rshen
# date: 2009-02-27

from __future__ import with_statement
from docutils import core
from optparse import OptionParser
from rstdirective import *
from utils import *
import tempfile
import BeautifulSoup as bs
import sys

def to_html(rst_file, options):
    f = open(rst_file)
    contents = f.read()
    parts = core.publish_parts(writer_name='html', source=contents)
    html_content = unicode(parts['whole']).encode('utf-8')
    tmp_file, tmp_path = tempfile.mkstemp()
    with open(tmp_path, 'w') as tmp:
        tmp.write(html_content)
    print "HTML Contents written to %s" % tmp_path

    return bs.BeautifulSoup(html_content)

def gen_blog(html_tree, options):
    content = html_tree.find("div")
    title = content.find("h1")
    title.extract()
    content.find("table", { "class" : "docinfo" }).extract()

    sections = content.findAll("div", { "class" : "section" })
    sections_html = ''.join([''.join([str(c) for c in sec.contents])\
            for sec in sections])
    return (''.join(title.contents), sections_html)

def upload_blog(title, html_content, options):
    post_blogspot(title, html_content, options.username, options.password)

Friday, 27 February 2009

中文测试

中文测试标题

该帖子纯粹为了测试中文,如果成功,将在博客上显示正确地中文~~

Wednesday, 25 February 2009

Test

This is a test blog post

If this appears on my Blogspot, then my python script for posting blogs is successful. The following is a test for Pygments code highlighting.

for a in [5, 4, 3, 2, 1]:
    print a

Tuesday, 17 February 2009

流程图生成器(2)

最近用pyparsing写好了流程图定义的模块:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# flow_parser.py
# author: Rongzhou Shen
# date: 2009-02-11

from pyparsing import *

LSQUARE, RSQUARE, SEMI, ASSIGN, QUOTE = map(Suppress, '[];="')
LBRACE, RBRACE, AT, LBRACK, RBRACK = map(Suppress, '{}@()')

# Definition part of the flow_def file
node_id = Word(alphanums + "_")("id")
node_type = Word(alphas)("type")
declaration = node_type + LSQUARE + node_id + RSQUARE
value = QuotedString('"')
definition = Group(declaration("declaration") + ASSIGN + value("value") + SEMI)

# Flow part of the flow_def file
flow = Group(node_id + OneOrMore(Group('=>' + node_id)) + SEMI)

# Grammar definition of the whole file
flow_impl = OneOrMore(definition)("definitions") + OneOrMore(flow)("flows")
flow_def = AT + LBRACK + QuotedString('"')("flow_name") + RBRACK + LBRACE +\
        flow_impl("flow_impl") + RBRACE

#sample = """
#@("A test flow_chart") {
#    process[node1] = "This is a test process";
#    process[node2] = "This is another test process";
#    a => b => c;
#    ffew => fweji;
#}"""

def test_parse():
    expected = ['process', '[', 'node1', ']', '=', '"',
            'This is a test process', '"', ';']
    test_def = 'process[node1] = "This is a test process";'
    data = definition.parseString(test_def)
    assert len(data) == len(expected)
    assert all([x == y for x, y in zip(expected, data)])

    expected = ['a', '=>', 'b', '=>', 'c', ';']
    test_flow = 'a => b => c;'
    data = flow.parseString(test_flow)
    assert len(data) == len(expected)
    assert all([x == y for x, y in zip(expected, data)])

Tuesday, 10 February 2009

流程图生成器

一直想写一个来着,趁着这次在python.ubuntu.org.cn写[常用库系列]的机会写了这个。目前还不太完善,不过会慢慢改进。接下来要做的就是用pyparsing来完善和扩展我的流程图定义文件。
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import with_statement
import yapgvb
import optparse

FORMATS = {"png" : yapgvb.formats.png,
        "jpg" : yapgvb.formats.jpg,
        "gif" : yapgvb.formats.gif}
ENGINES = {"dot" : yapgvb.engines.dot,
        "neato" : yapgvb.engines.neato,
        "circo" : yapgvb.engines.circo,
        "twopi" : yapgvb.engines.twopi}

if __name__ == '__main__':
#    args = sys.argv
#    if len(args) < 2:
#        print "Usage: python state_machine.py <def file>"
#        sys.exit(0)

    parser = optparse.OptionParser()
    parser.add_option("-f", "--format", dest="format",
            help="store the flow chart in FORMAT",
            metavar="FORMAT", default="png")
    parser.add_option("-o", "--output", dest="output",
            help="save the flow chart to FILE",
            metavar="FILE")
    parser.add_option("-e", "--engine", dest="engine",
            help="the layout ENGINE to use for the flow chart",
            metavar="ENGINE", default="dot")

    options, args = parser.parse_args()
    if len(args) < 1:
        parser.print_usage()
        sys.exit(0)

    graph = yapgvb.Graph("States")
    node_dict = {}

    with open(args[0]) as def_file:
        lines = [l.strip() for l in def_file.readlines()]
        for line in lines:
            nodes = line.split("=>")
            prev_node = None
            for node in nodes[::-1]:
                label = node.strip().split("#")
                if not node_dict.has_key(label[0]):
                    node_in_graph = graph.add_node(label=label[1],
                            shape='ellipse', fillcolor='yellow',
                            style='filled', width=0.5)
                    node_dict[label[0]] = node_in_graph
                else:
                    node_in_graph = node_dict[label[0]]

                if prev_node:
                    edge = node_in_graph - prev_node
                    edge.arrowhead = 'normal'

                prev_node = node_in_graph

    graph.layout(ENGINES[options.engine])
    format = FORMATS[options.format]
    if options.output:
        out_file = options.output
    else:
        out_file = args[0] + "." + format

    graph.render(out_file, format)

Wednesday, 28 January 2009

Vim和Shell/GNU Screen搭配的困扰

一直使用Vim与GNU Screen搭配编程,有几个原因: 1. 命令行上的Vim和GVim在功能上几乎没有区别(某些插件是只能用GVim,不过我没有); 2. GNU Screen在重启X时不会影响其中程序的运行; 3. 一直用命令行的话,我可以随时关注命令行上其他程序的情况,例如mutt有无新的邮件,irssi有无新的消息; 但一直有一个问题困扰着我,就是命令行下的Vim屏幕滚动非常缓慢,按住j不动一直到屏幕上最后一行,然后松手,光标会继续往下走十几行甚至几十行,这让我很不爽。 今天作了一个实验,得出以下结果: 1. GNU Screen打开与否和这个现象无关; 2. 此现象只有在分裂Vim窗口时才会出现; 3. GVim没有这个现象; 在网上搜索了一番,暂时没有找到解决的办法。因为GVim没有这个问题,所以暂时用GVim代替。GVim的问题在于离开命令行我就无法得知某些命令行程序的信息。如果能找到解决办法就好了